Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/quicktype-core/src/attributes/Constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ export const minMaxLengthTypeAttributeKind: TypeAttributeKind<MinMaxConstraint>
"maxLength",
);

export const minMaxItemsTypeAttributeKind: TypeAttributeKind<MinMaxConstraint> =
new MinMaxConstraintTypeAttributeKind(
"minMaxItems",
new Set<TypeKind>(["array"]),
"minItems",
"maxItems",
);

export const minMaxContainsTypeAttributeKind: TypeAttributeKind<MinMaxConstraint> =
new MinMaxConstraintTypeAttributeKind(
"minMaxItems",
new Set<TypeKind>(["array"]),
"minContains",
"maxContains",
);

function producer(
schema: JSONSchema,
minProperty: string,
Expand Down Expand Up @@ -179,6 +195,30 @@ export function minMaxLengthAttributeProducer(
};
}

export function minMaxItemsAttributeProducer(
schema: JSONSchema,
_ref: Ref,
types: Set<JSONSchemaType>
): JSONSchemaAttributes | undefined {
if (!types.has("array")) return undefined;

const maybeMinMaxLength = producer(schema, "minItems", "maxItems");
if (maybeMinMaxLength === undefined) return undefined;
return { forArray: minMaxItemsTypeAttributeKind.makeAttributes(maybeMinMaxLength) };
}

export function minMaxContainsAttributeProducer(
schema: JSONSchema,
_ref: Ref,
types: Set<JSONSchemaType>
): JSONSchemaAttributes | undefined {
if (!types.has("array")) return undefined;

const maybeMinMaxLength = producer(schema, "minContains", "maxContains");
if (maybeMinMaxLength === undefined) return undefined;
return { forArray: minMaxContainsTypeAttributeKind.makeAttributes(maybeMinMaxLength) };
}

export function minMaxValueForType(t: Type): MinMaxConstraint | undefined {
return minMaxTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}
Expand All @@ -187,6 +227,14 @@ export function minMaxLengthForType(t: Type): MinMaxConstraint | undefined {
return minMaxLengthTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export function minMaxItemsForType(t: Type): MinMaxConstraint | undefined {
return minMaxItemsTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export function minMaxContainsForType(t: Type): MinMaxConstraint | undefined {
return minMaxContainsTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export class PatternTypeAttributeKind extends TypeAttributeKind<string> {
public constructor() {
super("pattern");
Expand Down
23 changes: 16 additions & 7 deletions packages/quicktype-core/src/input/JSONSchemaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import URI from "urijs";
import { accessorNamesAttributeProducer } from "../attributes/AccessorNames";
import {
minMaxAttributeProducer,
minMaxContainsAttributeProducer,
minMaxItemsAttributeProducer,
minMaxLengthAttributeProducer,
patternAttributeProducer,
} from "../attributes/Constraints";
Expand Down Expand Up @@ -649,6 +651,7 @@ const schemaTypes = Object.getOwnPropertyNames(
) as JSONSchemaType[];

export interface JSONSchemaAttributes {
forArray?: TypeAttributes;
forCases?: TypeAttributes[];
forNumber?: TypeAttributes;
forObject?: TypeAttributes;
Expand Down Expand Up @@ -1032,8 +1035,7 @@ async function addTypesInSchema(
return typeBuilder.getPrimitiveType(kind, attributes);
}

async function makeArrayType(): Promise<TypeRef> {
const singularAttributes = singularizeTypeNames(typeAttributes);
async function makeArrayType(attributes: TypeAttributes): Promise<TypeRef> {
const items = schema.items;
let itemType: TypeRef;
if (Array.isArray(items)) {
Expand All @@ -1043,19 +1045,19 @@ async function addTypesInSchema(
return await toType(
checkJSONSchema(item, itemLoc.canonicalRef),
itemLoc,
singularAttributes,
attributes,
);
});
itemType = typeBuilder.getUnionType(
emptyTypeAttributes,
attributes,
new Set(itemTypes),
);
} else if (typeof items === "object") {
const itemsLoc = loc.push("items");
itemType = await toType(
checkJSONSchema(items, itemsLoc.canonicalRef),
itemsLoc,
singularAttributes,
attributes,
);
} else if (items !== undefined && items !== true) {
return messageError(
Expand All @@ -1066,7 +1068,7 @@ async function addTypesInSchema(
itemType = typeBuilder.getPrimitiveType("any");
}

typeBuilder.addAttributes(itemType, singularAttributes);
typeBuilder.addAttributes(itemType, attributes);
return typeBuilder.getArrayType(emptyTypeAttributes, itemType);
}

Expand Down Expand Up @@ -1283,7 +1285,12 @@ async function addTypesInSchema(
}

if (includeArray) {
unionTypes.push(await makeArrayType());
const arrayAttributes = combineTypeAttributes(
"union",
inferredAttributes,
combineProducedAttributes(({ forArray }) => forArray),
);
unionTypes.push(await makeArrayType(arrayAttributes));
}

if (includeObject) {
Expand Down Expand Up @@ -1515,6 +1522,8 @@ export class JSONSchemaInput implements Input<JSONSchemaSourceData> {
uriSchemaAttributesProducer,
minMaxAttributeProducer,
minMaxLengthAttributeProducer,
minMaxItemsAttributeProducer,
minMaxContainsAttributeProducer,
patternAttributeProducer,
].concat(additionalAttributeProducers);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { minMaxItemsForType } from "../../attributes/Constraints";
import { type Name, type Namer, funPrefixNamer } from "../../Naming";
import type { RenderContext } from "../../Renderer";
import type { OptionValues } from "../../RendererOptions";
Expand Down Expand Up @@ -72,14 +73,35 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
(_stringType) => singleWord("string"),
(arrayType) => {
const itemType = this.sourceFor(arrayType.items);
const minMaxItems = minMaxItemsForType(arrayType.items);
if (
(arrayType.items instanceof UnionType &&
!this._tsFlowOptions.declareUnions) ||
arrayType.items instanceof ArrayType
) {
if (minMaxItems?.[0] && minMaxItems[0] > 0) {
return singleWord([
"[",
itemType.source,
", ...",
itemType.source,
"[]]",
]);
}

return singleWord(["Array<", itemType.source, ">"]);
}

if (minMaxItems?.[0] && minMaxItems[0] > 0) {
return singleWord([
"[",
parenIfNeeded(itemType),
", ...",
parenIfNeeded(itemType),
"[]]",
]);
}

return singleWord([parenIfNeeded(itemType), "[]"]);
},
(_classType) => panic("We handled this above"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { arrayIntercalate } from "collection-utils";

import { minMaxItemsForType } from "../../attributes/Constraints";
import { ConvenienceRenderer } from "../../ConvenienceRenderer";
import { type Name, type Namer, funPrefixNamer } from "../../Naming";
import type { RenderContext } from "../../Renderer";
Expand Down Expand Up @@ -111,11 +112,32 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer {
(_integerType) => "z.number()",
(_doubleType) => "z.number()",
(_stringType) => "z.string()",
(arrayType) => [
"z.array(",
this.typeMapTypeFor(arrayType.items, false),
")",
],
(arrayType) => {
const minMaxItems = minMaxItemsForType(arrayType.items);

const arrayString: Sourcelike[] = [
"z.array(",
this.typeMapTypeFor(arrayType.items, false),
")",
];
if (minMaxItems?.[0]) {
arrayString.push(
".min(",
minMaxItems[0].toString(10),
")",
);
}

if (minMaxItems?.[1]) {
arrayString.push(
".max(",
minMaxItems[1].toString(10),
")",
);
}

return arrayString;
},
(_classType) => panic("Should already be handled."),
(_mapType) => [
"z.record(z.string(), ",
Expand Down
Loading