diff --git a/README.md b/README.md index 236f887..ac1a18c 100644 --- a/README.md +++ b/README.md @@ -66,19 +66,20 @@ You can pass a string as the second parameter of the main zodToJsonSchema functi Instead of the schema name (or nothing), you can pass an options object as the second parameter. The following options are available: -| Option | Effect | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **name**?: _string_ | As described above. | -| **basePath**?: string[] | The base path of the root reference builder. Defaults to ["#"]. | +| Option | Effect | +| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **name**?: _string_ | As described above. | +| **basePath**?: string[] | The base path of the root reference builder. Defaults to ["#"]. | | **$refStrategy**?: "root" \| "relative" \| "seen" \| "none" | The reference builder strategy; Defaults to "root". | -| **effectStrategy**?: "input" \| "any" | The effects output strategy. Defaults to "input". _See known issues!_ | -| **definitionPath**?: "definitions" \| "$defs" | The name of the definitions property when name is passed. Defaults to "definitions". | -| **target**?: "jsonSchema7" \| "jsonSchema2019-09" \| "openApi3" | Which spec to target. Defaults to "jsonSchema7" | -| **strictUnions**?: boolean | Scrubs unions of any-like json schemas, like `{}` or `true`. Multiple zod types may result in these out of necessity, such as z.instanceof() | -| **definitions**?: Record | See separate section below | -| **errorMessages**?: boolean | Include custom error messages created via chained function checks for supported zod types. See section below | -| **markdownDescription**?: boolean | Copies the `description` meta to `markdownDescription` | -| **emailStrategy**?: "format:email" \| "format:idn-email" \| "pattern:zod" | Choose how to handle the email string check. Defaults to "format:email". | +| **effectStrategy**?: "input" \| "any" | The effects output strategy. Defaults to "input". _See known issues!_ | +| **dateStrategy**?: "string" \| "integer" | Date strategy, integer allow to specify in unix-time min and max values. Defaults to "string". | +| **emailStrategy**?: "format:email" \| "format:idn-email" \| "pattern:zod" | Choose how to handle the email string check. Defaults to "format:email". | +| **definitionPath**?: "definitions" \| "$defs" | The name of the definitions property when name is passed. Defaults to "definitions". | +| **target**?: "jsonSchema7" \| "jsonSchema2019-09" \| "openApi3" | Which spec to target. Defaults to "jsonSchema7" | +| **strictUnions**?: boolean | Scrubs unions of any-like json schemas, like `{}` or `true`. Multiple zod types may result in these out of necessity, such as z.instanceof() | +| **definitions**?: Record | See separate section below | +| **errorMessages**?: boolean | Include custom error messages created via chained function checks for supported zod types. See section below | +| **markdownDescription**?: boolean | Copies the `description` meta to `markdownDescription` | ### Definitions diff --git a/src/Options.ts b/src/Options.ts index 3baad4d..d0aace2 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -8,6 +8,7 @@ export type Options = { basePath: string[]; effectStrategy: "input" | "any"; pipeStrategy: "input" | "all"; + dateStrategy: "string" | "integer"; target: Target; strictUnions: boolean; definitionPath: string; @@ -23,6 +24,7 @@ export const defaultOptions: Options = { basePath: ["#"], effectStrategy: "input", pipeStrategy: "all", + dateStrategy: "string", definitionPath: "definitions", target: "jsonSchema7", strictUnions: false, diff --git a/src/parseDef.ts b/src/parseDef.ts index 4eadcc3..0a995a4 100644 --- a/src/parseDef.ts +++ b/src/parseDef.ts @@ -183,7 +183,7 @@ const selectParser = ( case ZodFirstPartyTypeKind.ZodBoolean: return parseBooleanDef(); case ZodFirstPartyTypeKind.ZodDate: - return parseDateDef(); + return parseDateDef(def, refs); case ZodFirstPartyTypeKind.ZodUndefined: return parseUndefinedDef(); case ZodFirstPartyTypeKind.ZodNull: diff --git a/src/parsers/date.ts b/src/parsers/date.ts index 4301baa..63300cb 100644 --- a/src/parsers/date.ts +++ b/src/parsers/date.ts @@ -1,11 +1,62 @@ +import { ZodDateDef } from "zod"; +import { Refs } from "../Refs"; +import { ErrorMessages, setResponseValueAndErrors } from "../errorMessages"; +import { JsonSchema7NumberType } from "./number"; + export type JsonSchema7DateType = { - type: "string"; - format: "date-time"; + type: "integer" | "string"; + format: "unix-time" | "date-time"; + minimum?: number; + maximum?: number; + errorMessage?: ErrorMessages; }; -export function parseDateDef(): JsonSchema7DateType { - return { - type: "string", - format: "date-time", - }; +export function parseDateDef( + def: ZodDateDef, + refs: Refs +): JsonSchema7DateType { + if (refs.dateStrategy == "integer") { + return integerDateParser(def, refs); + } else { + return { + type: "string", + format: "date-time", + }; + } } + +const integerDateParser = (def: ZodDateDef, refs: Refs ) => { + const res: JsonSchema7DateType = { + type: "integer", + format: "unix-time", + }; + + for (const check of def.checks) { + switch (check.kind) { + case "min": + if (refs.target === "jsonSchema7") { + setResponseValueAndErrors( + res, + "minimum", + check.value, // This is in milliseconds + check.message, + refs + ); + } + break; + case "max": + if (refs.target === "jsonSchema7") { + setResponseValueAndErrors( + res, + "maximum", + check.value, // This is in milliseconds + check.message, + refs + ); + } + break; + } + } + + return res; +}; diff --git a/test/parsers/date.test.ts b/test/parsers/date.test.ts new file mode 100644 index 0000000..38d423b --- /dev/null +++ b/test/parsers/date.test.ts @@ -0,0 +1,83 @@ +import { JSONSchema7Type } from "json-schema"; +import { z } from "zod"; +import { parseDateDef } from "../../src/parsers/date"; +import { getRefs } from "../../src/Refs"; +import { errorReferences } from "./errorReferences"; +describe("Number validations", () => { + it("should be possible to date as a string type", () => { + const zodDateSchema = z.date(); + const parsedSchemaWithOption = parseDateDef(zodDateSchema._def, getRefs({ dateStrategy: 'string' })); + const parsedSchemaFromDefault = parseDateDef(zodDateSchema._def, getRefs()); + + const jsonSchema: JSONSchema7Type = { + type: "string", + format: "date-time", + }; + + expect(parsedSchemaWithOption).toStrictEqual(jsonSchema); + expect(parsedSchemaFromDefault).toStrictEqual(jsonSchema); + }); + + it("should be possible to describe minimum date", () => { + const zodDateSchema = z.date().min(new Date("1970-01-02"), { message: "Too old" }) + const parsedSchema = parseDateDef(zodDateSchema._def, getRefs({ dateStrategy: 'integer' })); + + const jsonSchema: JSONSchema7Type = { + type: "integer", + format: "unix-time", + minimum: 86400000, + }; + + expect(parsedSchema).toStrictEqual(jsonSchema); + }); + + it("should be possible to describe maximum date", () => { + const zodDateSchema = z.date().max(new Date("1970-01-02")); + const parsedSchema = parseDateDef(zodDateSchema._def, getRefs({ dateStrategy: 'integer' })); + + const jsonSchema: JSONSchema7Type = { + type: "integer", + format: "unix-time", + maximum: 86400000, + }; + + expect(parsedSchema).toStrictEqual(jsonSchema); + }); + + it("should be possible to describe both maximum and minimum date", () => { + const zodDateSchema = z.date().min(new Date("1970-01-02")).max(new Date("1972-01-02")); + const parsedSchema = parseDateDef(zodDateSchema._def, getRefs({ dateStrategy: 'integer' })); + + const jsonSchema: JSONSchema7Type = { + type: "integer", + format: "unix-time", + minimum: 86400000, + maximum: 63158400000, + }; + + expect(parsedSchema).toStrictEqual(jsonSchema); + }); + + it("should include custom error message for both maximum and minimum if they're passed", () => { + const minimumErrorMessage = 'To young'; + const maximumErrorMessage = 'To old'; + const zodDateSchema = z.date() + .min(new Date("1970-01-02"), minimumErrorMessage) + .max(new Date("1972-01-02"), maximumErrorMessage); + + const parsedSchema = parseDateDef(zodDateSchema._def, errorReferences({ dateStrategy: 'integer' })); + + const jsonSchema: JSONSchema7Type = { + type: "integer", + format: "unix-time", + minimum: 86400000, + maximum: 63158400000, + errorMessage: { + minimum: minimumErrorMessage, + maximum: maximumErrorMessage, + }, + }; + + expect(parsedSchema).toStrictEqual(jsonSchema); + }); +}); diff --git a/test/parsers/errorReferences.ts b/test/parsers/errorReferences.ts index d9331af..0a16dd9 100644 --- a/test/parsers/errorReferences.ts +++ b/test/parsers/errorReferences.ts @@ -1,7 +1,8 @@ +import { Options, Targets } from "../../src/Options"; import { getRefs, Refs } from "../../src/Refs"; -export function errorReferences(): Refs { - const r = getRefs(); +export function errorReferences(options?: string | Partial>): Refs { + const r = getRefs(options); r.errorMessages = true; return r; }