Skip to content

Commit

Permalink
JTD timestamp option (#1584)
Browse files Browse the repository at this point in the history
* add timestamp option for jtd

* tests for JTD timestamp option

* configurable timestamp check

* address feedback

* doc

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
  • Loading branch information
jrr and epoberezkin authored May 9, 2021
1 parent a60eff7 commit df964e4
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/json-type-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ It has a required member `type` and an optional members `nullable` and `metadata

- `"string"` - defines a string
- `"boolean"` - defines boolean value `true` or `false`
- `"timestamp"` - defines timestamp (JSON string, Ajv would also allow Date object with this type) according to [RFC3339](https://datatracker.ietf.org/doc/rfc3339/)
- `"timestamp"` - defines timestamp ( accepting either an [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) JSON string or a Date object, configurable via the `timestamp` Ajv option)
- `type` values that define integer numbers:
- `"int8"` - signed byte value (-128 .. 127)
- `"uint8"` - unsigned byte value (0 .. 255)
Expand Down
4 changes: 4 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ Option values:

Asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](./guide/managing-schemas.md#asynchronous-schema-compilation).

### timestamp

(JTD only) This governs what Javascript types will be accepted for the [JTD timestamp type](./json-type-definition#type-form). By default Ajv will accept either Date objects or [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) strings. You can adjust this behavior by specifying `timestamp: "date"` or `timestamp: "string"`.

## Options to modify validated data

### removeAdditional
Expand Down
2 changes: 2 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export interface CurrentOptions {
unevaluated?: boolean // NEW
dynamicRef?: boolean // NEW
jtd?: boolean // NEW
/** (JTD only) Accepted Javascript types for `timestamp` type */
timestamp?: "string" | "date"
meta?: SchemaObject | boolean
defaultMeta?: string | AnySchemaObject
validateSchema?: boolean | "log"
Expand Down
22 changes: 19 additions & 3 deletions lib/vocabularies/jtd/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import validTimestamp from "../../runtime/timestamp"
import {useFunc} from "../../compile/util"
import {checkMetadata} from "./metadata"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"
import {_Code} from "../../compile/codegen/code"

export type JTDTypeError = _JTDTypeError<"type", JTDType, JTDType>

Expand All @@ -26,22 +27,37 @@ const error: KeywordErrorDefinition = {
params: (cxt) => typeErrorParams(cxt, cxt.schema),
}

function timestampCode(cxt: KeywordCxt): _Code {
const {gen, data} = cxt
switch (cxt.it.opts.timestamp) {
case "date":
return _`${data} instanceof Date `
case "string": {
const vts = useFunc(gen, validTimestamp)
return _`typeof ${data} == "string" && ${vts}(${data})`
}
default: {
const vts = useFunc(gen, validTimestamp)
return _`${data} instanceof Date || (typeof ${data} == "string" && ${vts}(${data}))`
}
}
}

const def: CodeKeywordDefinition = {
keyword: "type",
schemaType: "string",
error,
code(cxt: KeywordCxt) {
checkMetadata(cxt)
const {gen, data, schema, parentSchema} = cxt
const {data, schema, parentSchema} = cxt
let cond: Code
switch (schema) {
case "boolean":
case "string":
cond = _`typeof ${data} == ${schema}`
break
case "timestamp": {
const vts = useFunc(gen, validTimestamp)
cond = _`${data} instanceof Date || (typeof ${data} == "string" && ${vts}(${data}))`
cond = timestampCode(cxt)
break
}
case "float32":
Expand Down
34 changes: 34 additions & 0 deletions spec/jtd-timestamps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import _AjvJTD from "./ajv_jtd"
import assert = require("assert")

describe("JTD Timestamps", () => {
it("Should accept dates or strings by default", () => {
const ajv = new _AjvJTD()
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), true)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), true)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})

it("Should enforce timestamp=string", () => {
const ajv = new _AjvJTD({timestamp: "string"})
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), false)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), true)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})

it("Should enforce timestamp=date", () => {
const ajv = new _AjvJTD({timestamp: "date"})
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), true)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), false)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})
})

0 comments on commit df964e4

Please sign in to comment.