diff --git a/docs/json-type-definition.md b/docs/json-type-definition.md index 507fe6ddb..9a05ed18c 100644 --- a/docs/json-type-definition.md +++ b/docs/json-type-definition.md @@ -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) diff --git a/docs/options.md b/docs/options.md index ef70ac7c1..ab288688f 100644 --- a/docs/options.md +++ b/docs/options.md @@ -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 diff --git a/lib/core.ts b/lib/core.ts index 25683a1c2..20892166e 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -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" diff --git a/lib/vocabularies/jtd/type.ts b/lib/vocabularies/jtd/type.ts index d28428199..87451476f 100644 --- a/lib/vocabularies/jtd/type.ts +++ b/lib/vocabularies/jtd/type.ts @@ -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> @@ -26,13 +27,29 @@ 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": @@ -40,8 +57,7 @@ const def: CodeKeywordDefinition = { 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": diff --git a/spec/jtd-timestamps.spec.ts b/spec/jtd-timestamps.spec.ts new file mode 100644 index 000000000..889fb74b8 --- /dev/null +++ b/spec/jtd-timestamps.spec.ts @@ -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) + }) +})