diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index d9d62c617..7de1a6758 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts @@ -1,6 +1,10 @@ import { arrayIntercalate } from "collection-utils"; -import { minMaxItemsForType } from "../../attributes/Constraints.js"; +import { + minMaxItemsForType, + minMaxLengthForType, + patternForType, +} from "../../attributes/Constraints.js"; import { ConvenienceRenderer } from "../../ConvenienceRenderer.js"; import { type Name, type Namer, funPrefixNamer } from "../../Naming.js"; import type { RenderContext } from "../../Renderer.js"; @@ -99,6 +103,28 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { return p.isOptional ? [typeMap, ".optional()"] : typeMap; } + protected renderString(type: Type, base: Sourcelike): Sourcelike { + const constraints: Sourcelike[] = [base]; + const [minimumLength, maximumLength] = minMaxLengthForType(type) ?? []; + const pattern = patternForType(type); + + if (minimumLength !== undefined) { + constraints.push(".min(", minimumLength.toString(10), ")"); + } + if (maximumLength !== undefined) { + constraints.push(".max(", maximumLength.toString(10), ")"); + } + if (pattern !== undefined) { + constraints.push( + ".regex(new RegExp(", + JSON.stringify(pattern), + "))", + ); + } + + return constraints; + } + protected typeMapTypeFor(t: Type, required = true): Sourcelike { if (["class", "object", "enum"].includes(t.kind)) { return [this.nameForNamedType(t), "Schema"]; @@ -111,7 +137,7 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { (_boolType) => "z.boolean()", (_integerType) => "z.number()", (_doubleType) => "z.number()", - (_stringType) => "z.string()", + (stringType) => this.renderString(stringType, "z.string()"), (arrayType) => { const [minItems, maxItems] = minMaxItemsForType(arrayType) ?? []; diff --git a/test/inputs/schema/minmaxlength.1.fail.minmaxlength.json b/test/inputs/schema/minmaxlength.1.fail.minmaxlength.json index 61c61f89d..b5b80bdba 100644 --- a/test/inputs/schema/minmaxlength.1.fail.minmaxlength.json +++ b/test/inputs/schema/minmaxlength.1.fail.minmaxlength.json @@ -2,6 +2,7 @@ "minlength": "aa", "maxlength": "bbb", "minmaxlength": "cccc", + "emptyOnly": "", "union": "dddddd", "inUnion": "cccc", "minMaxUnion": "eeeee", diff --git a/test/inputs/schema/minmaxlength.1.json b/test/inputs/schema/minmaxlength.1.json index b9ea9aba7..9cf4cab5e 100644 --- a/test/inputs/schema/minmaxlength.1.json +++ b/test/inputs/schema/minmaxlength.1.json @@ -2,6 +2,7 @@ "minlength": "aaaa", "maxlength": "bbb", "minmaxlength": "cccc", + "emptyOnly": "", "union": "dddddd", "inUnion": "cccc", "minMaxUnion": "eeeee", diff --git a/test/inputs/schema/minmaxlength.2.fail.minmaxlength.json b/test/inputs/schema/minmaxlength.2.fail.minmaxlength.json new file mode 100644 index 000000000..aaf427dca --- /dev/null +++ b/test/inputs/schema/minmaxlength.2.fail.minmaxlength.json @@ -0,0 +1,11 @@ +{ + "minlength": "aaaa", + "maxlength": "bbb", + "minmaxlength": "cccc", + "emptyOnly": "", + "union": "dd", + "inUnion": "cccc", + "minMaxUnion": "eeeee", + "intersection": "ffff", + "minMaxIntersection": "gggg" +} diff --git a/test/inputs/schema/minmaxlength.3.fail.minmaxlength.json b/test/inputs/schema/minmaxlength.3.fail.minmaxlength.json new file mode 100644 index 000000000..431dc03ce --- /dev/null +++ b/test/inputs/schema/minmaxlength.3.fail.minmaxlength.json @@ -0,0 +1,11 @@ +{ + "minlength": "aaaa", + "maxlength": "bbb", + "minmaxlength": "cccc", + "emptyOnly": "", + "union": "dddddd", + "inUnion": "cccc", + "minMaxUnion": "eeeee", + "intersection": "fff", + "minMaxIntersection": "gggg" +} diff --git a/test/inputs/schema/minmaxlength.4.fail.minmaxlength.json b/test/inputs/schema/minmaxlength.4.fail.minmaxlength.json new file mode 100644 index 000000000..f96e035df --- /dev/null +++ b/test/inputs/schema/minmaxlength.4.fail.minmaxlength.json @@ -0,0 +1,11 @@ +{ + "minlength": "aaaa", + "maxlength": "bbb", + "minmaxlength": "cccc", + "emptyOnly": "", + "union": "dddddd", + "inUnion": "cccc", + "minMaxUnion": "eeeee", + "intersection": "ffff", + "minMaxIntersection": "gggggg" +} diff --git a/test/inputs/schema/minmaxlength.5.fail.minmaxlength.json b/test/inputs/schema/minmaxlength.5.fail.minmaxlength.json new file mode 100644 index 000000000..739fc599c --- /dev/null +++ b/test/inputs/schema/minmaxlength.5.fail.minmaxlength.json @@ -0,0 +1,11 @@ +{ + "minlength": "aaaa", + "maxlength": "bbb", + "minmaxlength": "cccc", + "emptyOnly": "x", + "union": "dddddd", + "inUnion": "cccc", + "minMaxUnion": "eeeee", + "intersection": "ffff", + "minMaxIntersection": "gggg" +} diff --git a/test/inputs/schema/minmaxlength.6.fail.pattern.json b/test/inputs/schema/minmaxlength.6.fail.pattern.json new file mode 100644 index 000000000..c3fbd7119 --- /dev/null +++ b/test/inputs/schema/minmaxlength.6.fail.pattern.json @@ -0,0 +1,11 @@ +{ + "minlength": "aaaa", + "maxlength": "bbb", + "minmaxlength": "1234", + "emptyOnly": "", + "union": "dddddd", + "inUnion": "cccc", + "minMaxUnion": "eeeee", + "intersection": "ffff", + "minMaxIntersection": "gggg" +} diff --git a/test/inputs/schema/minmaxlength.schema b/test/inputs/schema/minmaxlength.schema index 147e5e0e5..6a52399f0 100644 --- a/test/inputs/schema/minmaxlength.schema +++ b/test/inputs/schema/minmaxlength.schema @@ -12,7 +12,12 @@ "minmaxlength": { "type": "string", "minLength": 3, - "maxLength": 5 + "maxLength": 5, + "pattern": "^[a-z]+$" + }, + "emptyOnly": { + "type": "string", + "maxLength": 0 }, "union": { "oneOf": [ @@ -71,6 +76,7 @@ "minlength", "maxlength", "minmaxlength", + "emptyOnly", "union", "inUnion", "minMaxUnion", diff --git a/test/inputs/schema/optional-constraints.1.json b/test/inputs/schema/optional-constraints.1.json index 87fb17cf6..a070dc1f5 100644 --- a/test/inputs/schema/optional-constraints.1.json +++ b/test/inputs/schema/optional-constraints.1.json @@ -2,6 +2,6 @@ "reqZeroMin": 0, "optInt": 0, "optDouble": 0.5, - "optString": "abc", + "optString": "abcdefghij", "optPattern": "abc" } diff --git a/test/inputs/schema/optional-constraints.3.fail.minmaxlength.json b/test/inputs/schema/optional-constraints.3.fail.minmaxlength.json new file mode 100644 index 000000000..8b032c690 --- /dev/null +++ b/test/inputs/schema/optional-constraints.3.fail.minmaxlength.json @@ -0,0 +1,4 @@ +{ + "reqZeroMin": 0, + "optString": "abcdefghijk" +} diff --git a/test/inputs/schema/pattern.1.fail.pattern.json b/test/inputs/schema/pattern.1.fail.pattern.json index a564c3fc2..e1ee6749f 100644 --- a/test/inputs/schema/pattern.1.fail.pattern.json +++ b/test/inputs/schema/pattern.1.fail.pattern.json @@ -1,5 +1,6 @@ { "pattern1": "qqq", "pattern2": "ba", + "escaped": "123.abc", "union": "ccc" } diff --git a/test/inputs/schema/pattern.1.json b/test/inputs/schema/pattern.1.json index 7a82d4eca..f2efb7e7b 100644 --- a/test/inputs/schema/pattern.1.json +++ b/test/inputs/schema/pattern.1.json @@ -1,5 +1,6 @@ { "pattern1": "aa", "pattern2": "ba", + "escaped": "123.abc", "union": "ccc" } diff --git a/test/inputs/schema/pattern.2.fail.pattern.json b/test/inputs/schema/pattern.2.fail.pattern.json new file mode 100644 index 000000000..7b7a22dc5 --- /dev/null +++ b/test/inputs/schema/pattern.2.fail.pattern.json @@ -0,0 +1,6 @@ +{ + "pattern1": "aa", + "pattern2": "ba", + "escaped": "123.abc", + "union": "qqq" +} diff --git a/test/inputs/schema/pattern.3.fail.pattern.json b/test/inputs/schema/pattern.3.fail.pattern.json new file mode 100644 index 000000000..33182aac9 --- /dev/null +++ b/test/inputs/schema/pattern.3.fail.pattern.json @@ -0,0 +1,6 @@ +{ + "pattern1": "aa", + "pattern2": "ba", + "escaped": "abc.def", + "union": "ccc" +} diff --git a/test/inputs/schema/pattern.schema b/test/inputs/schema/pattern.schema index 42154efba..619ee2811 100644 --- a/test/inputs/schema/pattern.schema +++ b/test/inputs/schema/pattern.schema @@ -9,6 +9,10 @@ "type": "string", "pattern": "b[.]*" }, + "escaped": { + "type": "string", + "pattern": "^\\d+\\.[a-z]+$" + }, "union": { "oneOf": [ { @@ -21,5 +25,5 @@ ] } }, - "required": ["pattern1", "pattern2", "union"] + "required": ["pattern1", "pattern2", "escaped", "union"] } diff --git a/test/languages.ts b/test/languages.ts index 5e721fad1..c2112cbb5 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -2037,7 +2037,15 @@ export const TypeScriptZodLanguage: Language = { "e8b04.json", ], allowMissingNull: false, - features: ["enum", "union", "no-defaults", "date-time", "minmaxitems"], + features: [ + "enum", + "union", + "no-defaults", + "date-time", + "minmaxitems", + "minmaxlength", + "pattern", + ], output: "TopLevel.ts", topLevel: "TopLevel", skipJSON: [