@amritk/generate-parsers 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI.md +4 -2
- package/README.md +87 -23
- package/dist/generators/build-schema.d.ts +7 -1
- package/dist/generators/build-schema.js +4 -2
- package/dist/generators/generate-files.d.ts +7 -0
- package/dist/generators/generate-files.js +4 -1
- package/dist/generators/generate-parser-function.d.ts +9 -1
- package/dist/generators/generate-parser-function.js +333 -128
- package/dist/generators/generate-schema-checks.js +9 -8
- package/dist/generators/generate-strict-assertion.d.ts +6 -0
- package/dist/generators/generate-strict-assertion.js +5 -4
- package/package.json +9 -4
|
@@ -5,6 +5,7 @@ import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
|
5
5
|
import { missingCheck, safeAccessor, safeKey } from "@amritk/helpers/safe-accessor";
|
|
6
6
|
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasItems, hasMaxItems, hasMaxProperties, hasMinItems, hasMinProperties, hasOneOf, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
7
7
|
import { unknownKeyCheck } from "@amritk/helpers/unknown-key-check";
|
|
8
|
+
import { DEFAULT_UNKNOWN_KEYS } from "@amritk/helpers/unknown-keys-strategy";
|
|
8
9
|
import { findDiscriminator } from "../helpers/find-discriminator.js";
|
|
9
10
|
import { getDefaultValue } from "../helpers/get-default-value.js";
|
|
10
11
|
import { getDiscriminatorValue } from "../helpers/get-discriminator-value.js";
|
|
@@ -177,6 +178,11 @@ const generateFallbackObject = (schema, useRefImports, typeName, suffix, subType
|
|
|
177
178
|
if (isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
|
|
178
179
|
return `{} as ${typeName}`;
|
|
179
180
|
}
|
|
181
|
+
if (isSchemaObject(schema) && hasAllOf(schema)) {
|
|
182
|
+
const composesConditional = schema.allOf.some((member) => isSchemaObject(member) && (hasRef(member) || "if" in member || "then" in member || "else" in member));
|
|
183
|
+
if (composesConditional)
|
|
184
|
+
return `{} as ${typeName}`;
|
|
185
|
+
}
|
|
180
186
|
const requiredProps = [];
|
|
181
187
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
182
188
|
const isRequired = isPropertyRequired(key, schema);
|
|
@@ -250,7 +256,8 @@ const strictContext = (ctx) => ({
|
|
|
250
256
|
useRefImports: ctx?.useRefImports ?? false,
|
|
251
257
|
suffix: ctx?.suffix ?? "",
|
|
252
258
|
...ctx?.rootSchema !== void 0 ? { rootSchema: ctx.rootSchema } : {},
|
|
253
|
-
...ctx?.stripUnknown !== void 0 ? { stripUnknown: ctx.stripUnknown } : {}
|
|
259
|
+
...ctx?.stripUnknown !== void 0 ? { stripUnknown: ctx.stripUnknown } : {},
|
|
260
|
+
...ctx?.unknownKeys !== void 0 ? { unknownKeys: ctx.unknownKeys } : {}
|
|
254
261
|
});
|
|
255
262
|
const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
|
|
256
263
|
const functionName = generateParserName(typeName);
|
|
@@ -368,11 +375,12 @@ ${delegatedBody(itemParserName)}
|
|
|
368
375
|
const useRefImports = unionCtx?.useRefImports ?? false;
|
|
369
376
|
const suffix = unionCtx?.suffix ?? "";
|
|
370
377
|
const stripUnknown = unionCtx?.stripUnknown ?? false;
|
|
371
|
-
const
|
|
378
|
+
const unknownKeys = unionCtx?.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
|
|
379
|
+
const itemShapeValidator = generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved, unknownKeys);
|
|
372
380
|
const preamble = [
|
|
373
381
|
`type ${itemName} = NonNullable<${typeName}>[number];`,
|
|
374
382
|
itemShapeValidator,
|
|
375
|
-
generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator)
|
|
383
|
+
generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator, unknownKeys)
|
|
376
384
|
].join("\n\n");
|
|
377
385
|
return `${preamble}
|
|
378
386
|
|
|
@@ -548,13 +556,21 @@ const exactKeyCountOf = (schema) => {
|
|
|
548
556
|
return null;
|
|
549
557
|
return declaredCount;
|
|
550
558
|
};
|
|
559
|
+
const KEY_COUNT_VAR = "_keyCount";
|
|
560
|
+
const keyCountCheck = (unknownKeys, count, indent, obj = "input") => unknownKeys === "count-keys" ? {
|
|
561
|
+
statements: [],
|
|
562
|
+
verdict: `Object.getPrototypeOf(${obj}) === Object.prototype && Object.keys(${obj}).length === ${count}`
|
|
563
|
+
} : {
|
|
564
|
+
statements: [`${indent}let ${KEY_COUNT_VAR} = 0;`, `${indent}for (const _k in ${obj}) ${KEY_COUNT_VAR}++;`],
|
|
565
|
+
verdict: `${KEY_COUNT_VAR} === ${count}`
|
|
566
|
+
};
|
|
551
567
|
const shouldCacheVariable = (propSchema) => {
|
|
552
568
|
return isSchemaObject(propSchema);
|
|
553
569
|
};
|
|
554
570
|
const INLINE_NESTED_MAX_PROPERTIES = 12;
|
|
555
571
|
const INLINE_NESTED_MAX_SITES = 4;
|
|
556
572
|
const INLINE_NESTED_MAX_TOTAL_PROPERTIES = 24;
|
|
557
|
-
const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImports, suffix, stripUnknown, reservedNames) => {
|
|
573
|
+
const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImports, suffix, stripUnknown, reservedNames, claimVarName) => {
|
|
558
574
|
if (!isSchemaObject(childSchema) || !hasProperties(childSchema))
|
|
559
575
|
return null;
|
|
560
576
|
if (!stripUnknown)
|
|
@@ -576,6 +592,7 @@ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImpo
|
|
|
576
592
|
const keys = Object.keys(props);
|
|
577
593
|
if (keys.length === 0 || keys.length > INLINE_NESTED_MAX_PROPERTIES)
|
|
578
594
|
return null;
|
|
595
|
+
const declarations = [];
|
|
579
596
|
const checks = [];
|
|
580
597
|
const fields = [];
|
|
581
598
|
const readRoot = `(${objectExpr} as Record<string, any>)`;
|
|
@@ -584,22 +601,64 @@ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImpo
|
|
|
584
601
|
if (shouldUseRefImport(propSchema, useRefImports) || shouldUseArrayRefImport(propSchema, useRefImports) || shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
585
602
|
return null;
|
|
586
603
|
}
|
|
587
|
-
const accessor =
|
|
604
|
+
const accessor = claimVarName(`${objectExpr}_${toVarName(key).slice(1)}`);
|
|
605
|
+
declarations.push(` const ${accessor} = ${safeAccessor(readRoot, key)};`);
|
|
588
606
|
const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
|
|
589
607
|
if (check === null)
|
|
590
608
|
return null;
|
|
591
609
|
checks.push(check);
|
|
592
610
|
fields.push(`${safeLiteralKey(key)}: ${accessor}`);
|
|
593
611
|
}
|
|
594
|
-
const condition =
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
612
|
+
const condition = checks.join(" && ");
|
|
613
|
+
return { declarations, condition, literal: `{ ${fields.join(", ")} }`, propertyCount: keys.length };
|
|
614
|
+
};
|
|
615
|
+
const RESULT_VARIABLE = "out";
|
|
616
|
+
const PROTOTYPE_MEMBERS = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
617
|
+
const resultMember = (key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? `${RESULT_VARIABLE}.${key}` : `${RESULT_VARIABLE}[${JSON.stringify(key)}]`;
|
|
618
|
+
const renderLiteralEntry = (entry, indent) => {
|
|
619
|
+
if (entry.kind === "field")
|
|
620
|
+
return `${indent}${safeLiteralKey(entry.key)}: ${entry.value},`;
|
|
621
|
+
if (entry.kind === "spread")
|
|
622
|
+
return `${indent}...${entry.expression},`;
|
|
623
|
+
return `${indent}...(${entry.read} !== undefined && { ${safeLiteralKey(entry.key)}: ${entry.value} }),`;
|
|
624
|
+
};
|
|
625
|
+
const renderResultObject = (entries, typeName, guard) => {
|
|
626
|
+
const firstOptional = entries.findIndex((entry) => entry.kind === "optional");
|
|
627
|
+
const assignable = entries.every((entry, index) => index < firstOptional || entry.kind === "spread" || !PROTOTYPE_MEMBERS.has(entry.key));
|
|
628
|
+
if (firstOptional === -1 || !assignable) {
|
|
629
|
+
const open = guard === void 0 ? " return {" : ` if (${guard}) return {`;
|
|
630
|
+
return [
|
|
631
|
+
open,
|
|
632
|
+
entries.map((entry) => renderLiteralEntry(entry, " ")).join("\n"),
|
|
633
|
+
` } as unknown as ${typeName};`
|
|
634
|
+
];
|
|
635
|
+
}
|
|
636
|
+
const indent = guard === void 0 ? " " : " ";
|
|
637
|
+
const lines = [];
|
|
638
|
+
if (guard !== void 0)
|
|
639
|
+
lines.push(` if (${guard}) {`);
|
|
640
|
+
const literal = entries.slice(0, firstOptional);
|
|
641
|
+
if (literal.length === 0) {
|
|
642
|
+
lines.push(`${indent}const ${RESULT_VARIABLE}: Record<string, unknown> = {};`);
|
|
643
|
+
} else {
|
|
644
|
+
lines.push(`${indent}const ${RESULT_VARIABLE}: Record<string, unknown> = {`);
|
|
645
|
+
lines.push(literal.map((entry) => renderLiteralEntry(entry, `${indent} `)).join("\n"));
|
|
646
|
+
lines.push(`${indent}};`);
|
|
647
|
+
}
|
|
648
|
+
for (const entry of entries.slice(firstOptional)) {
|
|
649
|
+
if (entry.kind === "field")
|
|
650
|
+
lines.push(`${indent}${resultMember(entry.key)} = ${entry.value};`);
|
|
651
|
+
else if (entry.kind === "optional") {
|
|
652
|
+
lines.push(`${indent}if (${entry.read} !== undefined) ${resultMember(entry.key)} = ${entry.value};`);
|
|
653
|
+
} else
|
|
654
|
+
lines.push(`${indent}Object.assign(${RESULT_VARIABLE}, ${entry.expression});`);
|
|
655
|
+
}
|
|
656
|
+
lines.push(`${indent}return ${RESULT_VARIABLE} as unknown as ${typeName};`);
|
|
657
|
+
if (guard !== void 0)
|
|
658
|
+
lines.push(" }");
|
|
659
|
+
return lines;
|
|
601
660
|
};
|
|
602
|
-
const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource) => {
|
|
661
|
+
const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
|
|
603
662
|
const functionName = generateParserName(typeName);
|
|
604
663
|
const exportPrefix = exported ? "export " : "";
|
|
605
664
|
if (!hasProperties(schema)) {
|
|
@@ -618,21 +677,21 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
618
677
|
for (const [key, subName] of subTypeNames) {
|
|
619
678
|
const propSchema = schemaProps[key];
|
|
620
679
|
preamble.push(`type ${subName} = NonNullable<${typeName}>[${JSON.stringify(key)}];`);
|
|
621
|
-
const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
|
|
680
|
+
const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames, unknownKeys);
|
|
622
681
|
preamble.push(subShapeValidator);
|
|
623
|
-
preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator));
|
|
682
|
+
preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator, unknownKeys));
|
|
624
683
|
}
|
|
625
684
|
for (const [key, subName] of subItemNames) {
|
|
626
685
|
const propSchema = schemaProps[key];
|
|
627
686
|
const itemSchema = propSchema.items;
|
|
628
687
|
preamble.push(`type ${subName} = NonNullable<NonNullable<${typeName}>[${JSON.stringify(key)}]>[number];`);
|
|
629
|
-
const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
|
|
688
|
+
const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames, unknownKeys);
|
|
630
689
|
preamble.push(itemShapeValidator);
|
|
631
690
|
preamble.push(`const _every${subName} = (arr: readonly unknown[]): boolean => {
|
|
632
691
|
for (let i = 0; i < arr.length; i++) if (!${shapeValidatorName(subName)}(arr[i])) return false;
|
|
633
692
|
return true;
|
|
634
693
|
};`);
|
|
635
|
-
preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, itemShapeValidator));
|
|
694
|
+
preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, itemShapeValidator, unknownKeys));
|
|
636
695
|
}
|
|
637
696
|
const strictKeys = hasStrictKeys(schema);
|
|
638
697
|
const stripKeys = strictKeys || stripUnknown;
|
|
@@ -663,7 +722,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
663
722
|
let canFastPath = !hasAllOfRefParsers && !hasFastPathBlockingKeyword(schema) && !(strict && inlineAllOfMembers(schema).length > 0);
|
|
664
723
|
const fastPathChecks = [];
|
|
665
724
|
const fastPathAccessorChecks = [];
|
|
666
|
-
const usedVarNames = /* @__PURE__ */ new Set();
|
|
725
|
+
const usedVarNames = /* @__PURE__ */ new Set([KEY_COUNT_VAR]);
|
|
726
|
+
let inlinedGuardSites = 0;
|
|
727
|
+
let inlinedGuardProperties = 0;
|
|
667
728
|
for (const key of propertyKeys) {
|
|
668
729
|
const propSchema = schemaProps[key];
|
|
669
730
|
const isRequired = isPropertyRequired(key, schema);
|
|
@@ -679,7 +740,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
679
740
|
continue;
|
|
680
741
|
}
|
|
681
742
|
const subName = subTypeNames.get(key);
|
|
682
|
-
|
|
743
|
+
const inlined = subName !== void 0 && inlinedGuardSites < INLINE_NESTED_MAX_SITES ? inlineNestedShapeCheck(propSchema, subName, varName, useRefImports, suffix, stripUnknown, reservedNames, unknownKeys) : null;
|
|
744
|
+
const withinBudget = inlined !== null && inlinedGuardProperties + inlined.propertyCount <= INLINE_NESTED_MAX_TOTAL_PROPERTIES;
|
|
745
|
+
if (withinBudget) {
|
|
746
|
+
inlinedGuardSites++;
|
|
747
|
+
inlinedGuardProperties += inlined.propertyCount;
|
|
748
|
+
}
|
|
749
|
+
let check = withinBudget ? inlined.check : subName ? `${shapeValidatorName(subName)}(${varName})` : generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
|
|
683
750
|
const itemSubName = subItemNames.get(key);
|
|
684
751
|
if (check !== null && itemSubName) {
|
|
685
752
|
check = `${check} && _every${itemSubName}(${varName})`;
|
|
@@ -711,32 +778,24 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
711
778
|
fastPathAccessorChecks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
|
|
712
779
|
}
|
|
713
780
|
}
|
|
714
|
-
|
|
715
|
-
|
|
781
|
+
const fastPathKeyCount = fastPathNeedsKnownKeys ? exactKeyCount : null;
|
|
782
|
+
if (fastPathNeedsKnownKeys && exactKeyCount === null) {
|
|
783
|
+
fastPathChecks.push(`_hasOnlyKnownKeys${typeName}(input)`);
|
|
716
784
|
}
|
|
717
|
-
const deepGuard = canFastPath && fastPathChecks.length > 0 ? fastPathChecks.join(" && ") : null;
|
|
785
|
+
const deepGuard = canFastPath && (fastPathChecks.length > 0 || fastPathKeyCount !== null) ? fastPathChecks.join(" && ") : null;
|
|
718
786
|
const literalReturnUsesVars = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema)) && exported;
|
|
719
787
|
let deepGuardExpr = deepGuard;
|
|
720
788
|
if (deepGuard !== null && shapeValidatorSource !== void 0 && !literalReturnUsesVars && !droppedKnownKeysTerm) {
|
|
721
789
|
const shapeFnName = shapeValidatorName(typeName);
|
|
722
790
|
const shapeStrictKeysGuard = stripKeys && exactKeyCount === null ? `
|
|
723
791
|
if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
|
|
724
|
-
const
|
|
725
|
-
if (stripKeys && exactKeyCount !== null) {
|
|
726
|
-
shapeChecks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}`);
|
|
727
|
-
}
|
|
728
|
-
const expectedShapeValidator = shapeChecks.length === 0 ? shapeStrictKeysGuard ? `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
|
|
729
|
-
if (!isObject(input)) return false;${shapeStrictKeysGuard}
|
|
730
|
-
return true;
|
|
731
|
-
};` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => isObject(input);` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
|
|
732
|
-
if (!isObject(input)) return false;${shapeStrictKeysGuard}
|
|
733
|
-
return ${shapeChecks.join("\n && ")};
|
|
734
|
-
};`;
|
|
792
|
+
const expectedShapeValidator = renderShapeValidator(exportPrefix, shapeFnName, fastPathAccessorChecks, stripKeys ? exactKeyCount : null, shapeStrictKeysGuard, unknownKeys);
|
|
735
793
|
if (shapeValidatorSource === expectedShapeValidator) {
|
|
736
794
|
deepGuardExpr = `${shapeFnName}(input)`;
|
|
737
795
|
}
|
|
738
796
|
}
|
|
739
797
|
const deepGuardCallsShape = deepGuardExpr !== deepGuard;
|
|
798
|
+
const inlineKeyCount = deepGuardCallsShape ? null : fastPathKeyCount;
|
|
740
799
|
let canShallowGuard = strict && stripUnknown && !strictKeys && !hasFastPathBlockingKeyword(schema);
|
|
741
800
|
const shallowChecks = [];
|
|
742
801
|
if (canShallowGuard) {
|
|
@@ -776,18 +835,25 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
776
835
|
warnLines.push(` }`);
|
|
777
836
|
warnLines.push(` }`);
|
|
778
837
|
}
|
|
779
|
-
const
|
|
780
|
-
|
|
838
|
+
const claimNestedVarName = (base) => {
|
|
839
|
+
let name = base;
|
|
840
|
+
while (usedVarNames.has(name))
|
|
841
|
+
name = `${name}_`;
|
|
842
|
+
usedVarNames.add(name);
|
|
843
|
+
return name;
|
|
844
|
+
};
|
|
845
|
+
const buildObjectLines = (directAssign, hoistLines) => {
|
|
846
|
+
const entries = [];
|
|
781
847
|
let inlinedSites = 0;
|
|
782
848
|
let inlinedProperties = 0;
|
|
783
849
|
if (!stripKeys) {
|
|
784
|
-
|
|
850
|
+
entries.push({ kind: "spread", expression: "input" });
|
|
785
851
|
}
|
|
786
852
|
if (useRefImports && isSchemaObject(schema) && hasAllOf(schema)) {
|
|
787
853
|
for (const entry of schema.allOf) {
|
|
788
854
|
if (isSchemaObject(entry) && hasRef(entry)) {
|
|
789
855
|
const parserName = generateParserName(refToName(entry.$ref, suffix));
|
|
790
|
-
|
|
856
|
+
entries.push({ kind: "spread", expression: `(${parserName}(input) as Record<string, unknown>)` });
|
|
791
857
|
}
|
|
792
858
|
}
|
|
793
859
|
}
|
|
@@ -798,18 +864,23 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
798
864
|
if (subName) {
|
|
799
865
|
const subParserName = generateParserName(subName);
|
|
800
866
|
if (isRequired) {
|
|
801
|
-
const inlined = directAssign && !logWarnings && inlinedSites < INLINE_NESTED_MAX_SITES ? inlineNestedFastPath(propSchema, subName, accessor, useRefImports, suffix, stripUnknown, reservedNames) : null;
|
|
867
|
+
const inlined = hoistLines !== void 0 && directAssign && !logWarnings && inlinedSites < INLINE_NESTED_MAX_SITES ? inlineNestedFastPath(propSchema, subName, accessor, useRefImports, suffix, stripUnknown, reservedNames, claimNestedVarName) : null;
|
|
802
868
|
if (inlined !== null && inlinedProperties + inlined.propertyCount <= INLINE_NESTED_MAX_TOTAL_PROPERTIES) {
|
|
803
869
|
inlinedSites++;
|
|
804
870
|
inlinedProperties += inlined.propertyCount;
|
|
805
|
-
|
|
871
|
+
hoistLines?.push(...inlined.declarations);
|
|
872
|
+
entries.push({
|
|
873
|
+
kind: "field",
|
|
874
|
+
key,
|
|
875
|
+
value: `(${inlined.condition})
|
|
806
876
|
? ${inlined.literal}
|
|
807
|
-
: ${subParserName}(${accessor})
|
|
877
|
+
: ${subParserName}(${accessor})`
|
|
878
|
+
});
|
|
808
879
|
continue;
|
|
809
880
|
}
|
|
810
|
-
|
|
881
|
+
entries.push({ kind: "field", key, value: `${subParserName}(${accessor})` });
|
|
811
882
|
} else {
|
|
812
|
-
|
|
883
|
+
entries.push({ kind: "optional", key, read: accessor, value: `${subParserName}(${accessor})` });
|
|
813
884
|
}
|
|
814
885
|
continue;
|
|
815
886
|
}
|
|
@@ -817,9 +888,14 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
817
888
|
if (itemSubName) {
|
|
818
889
|
const itemParserName = generateParserName(itemSubName);
|
|
819
890
|
if (isRequired) {
|
|
820
|
-
|
|
891
|
+
entries.push({ kind: "field", key, value: `validateArray(${accessor}, ${itemParserName})` });
|
|
821
892
|
} else {
|
|
822
|
-
|
|
893
|
+
entries.push({
|
|
894
|
+
kind: "optional",
|
|
895
|
+
key,
|
|
896
|
+
read: accessor,
|
|
897
|
+
value: `validateArray(${accessor}, ${itemParserName})`
|
|
898
|
+
});
|
|
823
899
|
}
|
|
824
900
|
continue;
|
|
825
901
|
}
|
|
@@ -827,9 +903,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
827
903
|
const ref = propSchema.$ref;
|
|
828
904
|
const parserName = generateParserName(refToName(ref, suffix));
|
|
829
905
|
if (isRequired) {
|
|
830
|
-
|
|
906
|
+
entries.push({ kind: "field", key, value: `${parserName}(${accessor})` });
|
|
831
907
|
} else {
|
|
832
|
-
|
|
908
|
+
entries.push({ kind: "optional", key, read: accessor, value: `${parserName}(${accessor})` });
|
|
833
909
|
}
|
|
834
910
|
continue;
|
|
835
911
|
}
|
|
@@ -838,9 +914,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
838
914
|
const ref = items.$ref;
|
|
839
915
|
const parserName = generateParserName(refToName(ref, suffix));
|
|
840
916
|
if (isRequired) {
|
|
841
|
-
|
|
917
|
+
entries.push({ kind: "field", key, value: `validateArray(${accessor}, ${parserName})` });
|
|
842
918
|
} else {
|
|
843
|
-
|
|
919
|
+
entries.push({ kind: "optional", key, read: accessor, value: `validateArray(${accessor}, ${parserName})` });
|
|
844
920
|
}
|
|
845
921
|
continue;
|
|
846
922
|
}
|
|
@@ -849,21 +925,21 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
849
925
|
const ref = additionalProps.$ref;
|
|
850
926
|
const parserName = generateParserName(refToName(ref, suffix));
|
|
851
927
|
if (isRequired) {
|
|
852
|
-
|
|
928
|
+
entries.push({ kind: "field", key, value: `validateRecord(${accessor}, ${parserName})` });
|
|
853
929
|
} else {
|
|
854
|
-
|
|
930
|
+
entries.push({ kind: "optional", key, read: accessor, value: `validateRecord(${accessor}, ${parserName})` });
|
|
855
931
|
}
|
|
856
932
|
continue;
|
|
857
933
|
}
|
|
858
934
|
if (!isSchemaObject(propSchema)) {
|
|
859
935
|
if (propSchema === true) {
|
|
860
936
|
if (isRequired) {
|
|
861
|
-
|
|
937
|
+
entries.push({ kind: "field", key, value: accessor });
|
|
862
938
|
} else if (stripKeys) {
|
|
863
|
-
|
|
939
|
+
entries.push({ kind: "optional", key, read: accessor, value: accessor });
|
|
864
940
|
}
|
|
865
941
|
} else if (isRequired) {
|
|
866
|
-
|
|
942
|
+
entries.push({ kind: "field", key, value: "undefined" });
|
|
867
943
|
}
|
|
868
944
|
continue;
|
|
869
945
|
}
|
|
@@ -876,25 +952,47 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
876
952
|
valueExpr = generateValidationExpression(key, propSchema, defaultValue, true, void 0, void 0, shouldCache ? varName : void 0, knownNotUndefined, caseInsensitive);
|
|
877
953
|
}
|
|
878
954
|
if (isRequired) {
|
|
879
|
-
|
|
955
|
+
entries.push({ kind: "field", key, value: valueExpr });
|
|
880
956
|
} else {
|
|
881
|
-
|
|
957
|
+
entries.push({ kind: "optional", key, read: accessor, value: valueExpr });
|
|
882
958
|
}
|
|
883
959
|
}
|
|
884
|
-
return
|
|
960
|
+
return entries;
|
|
885
961
|
};
|
|
886
|
-
const emitReturn = (lines2,
|
|
887
|
-
lines2.push(
|
|
888
|
-
|
|
889
|
-
|
|
962
|
+
const emitReturn = (lines2, entries) => {
|
|
963
|
+
lines2.push(...renderResultObject(entries, typeName));
|
|
964
|
+
};
|
|
965
|
+
const withKeyCount = (lines2, guarded) => {
|
|
966
|
+
if (inlineKeyCount === null) {
|
|
967
|
+
lines2.push(...guarded(deepGuardExpr));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const count = keyCountCheck(unknownKeys, inlineKeyCount, " ");
|
|
971
|
+
if (count.statements.length === 0) {
|
|
972
|
+
lines2.push(...guarded(deepGuardExpr === "" ? count.verdict : `${deepGuardExpr} && ${count.verdict}`));
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const counted = guarded(count.verdict);
|
|
976
|
+
if (deepGuardExpr === "") {
|
|
977
|
+
lines2.push(...keyCountCheck(unknownKeys, inlineKeyCount, " ").statements, ...counted);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const nested = counted.map((line) => line.split("\n").map((part) => ` ${part}`).join("\n"));
|
|
981
|
+
lines2.push(` if (${deepGuardExpr}) {`, ...count.statements, ...nested, ` }`);
|
|
982
|
+
};
|
|
983
|
+
const emitGuardedReturn = (lines2, statement) => {
|
|
984
|
+
withKeyCount(lines2, (guard) => [` if (${guard}) ${statement}`]);
|
|
985
|
+
};
|
|
986
|
+
const emitGuardedResult = (lines2, fields) => {
|
|
987
|
+
withKeyCount(lines2, (guard) => renderResultObject(fields, typeName, guard));
|
|
890
988
|
};
|
|
891
989
|
const emitDeepGuardReturn = (lines2) => {
|
|
892
990
|
if (!fastPathBuildsDeclaredLiteral) {
|
|
893
|
-
lines2
|
|
991
|
+
emitGuardedReturn(lines2, `return { ...input } as ${typeName};`);
|
|
894
992
|
return;
|
|
895
993
|
}
|
|
896
994
|
if (!exported && fastPathNeedsKnownKeys) {
|
|
897
|
-
lines2
|
|
995
|
+
emitGuardedReturn(lines2, `return input as ${typeName};`);
|
|
898
996
|
return;
|
|
899
997
|
}
|
|
900
998
|
const fields = [];
|
|
@@ -902,33 +1000,90 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
902
1000
|
if (!isSchemaObject(propSchema)) {
|
|
903
1001
|
if (propSchema === true) {
|
|
904
1002
|
const acc = safeAccessor("input", key);
|
|
905
|
-
fields.push(isRequired ?
|
|
1003
|
+
fields.push(isRequired ? { kind: "field", key, value: acc } : { kind: "optional", key, read: acc, value: acc });
|
|
906
1004
|
} else if (isRequired) {
|
|
907
|
-
fields.push(
|
|
1005
|
+
fields.push({ kind: "field", key, value: "undefined" });
|
|
908
1006
|
}
|
|
909
1007
|
continue;
|
|
910
1008
|
}
|
|
911
1009
|
const accessor = shouldCacheVariable(propSchema) ? varName : safeAccessor("input", key);
|
|
912
|
-
fields.push(isRequired ?
|
|
1010
|
+
fields.push(isRequired ? { kind: "field", key, value: accessor } : { kind: "optional", key, read: accessor, value: accessor });
|
|
913
1011
|
}
|
|
914
|
-
lines2
|
|
915
|
-
lines2.push(fields.join("\n"));
|
|
916
|
-
lines2.push(` } as unknown as ${typeName};`);
|
|
1012
|
+
emitGuardedResult(lines2, fields);
|
|
917
1013
|
};
|
|
1014
|
+
const assertionLines = strict ? generateObjectStrictAssertion(schema, typeName, {
|
|
1015
|
+
useRefImports,
|
|
1016
|
+
suffix,
|
|
1017
|
+
stripUnknown,
|
|
1018
|
+
...rootSchema !== void 0 ? { rootSchema } : {}
|
|
1019
|
+
}) : [];
|
|
1020
|
+
const unknownKeyThrowLines = () => rejectsUnknownKeys ? [
|
|
1021
|
+
` for (const _k in input) {`,
|
|
1022
|
+
` if (${strictKeyCheck.isUnknown("_k")}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`,
|
|
1023
|
+
` }`
|
|
1024
|
+
] : [];
|
|
1025
|
+
const varsAfterGuard = deepGuardCallsShape && shallowGuard === null;
|
|
1026
|
+
const assertBodyLines = [...assertionLines.slice(1), ...shallowGuard === null ? unknownKeyThrowLines() : []];
|
|
1027
|
+
const canSplit = !logWarnings && strict === true && (deepGuard !== null || shallowGuard !== null) && assertBodyLines.length > 0;
|
|
1028
|
+
const assertName = `_assert${typeName}`;
|
|
1029
|
+
const assertObjectName = `_assert${typeName}Object`;
|
|
1030
|
+
const emitAssertObjectFunction = () => `const ${assertObjectName}: (input: unknown) => asserts input is Record<string, unknown> = (input) => {
|
|
1031
|
+
${assertionLines[0]}
|
|
1032
|
+
};`;
|
|
1033
|
+
const emitAssertFunction = () => `const ${assertName} = (input: Record<string, unknown>): void => {
|
|
1034
|
+
${assertBodyLines.join("\n")}
|
|
1035
|
+
};`;
|
|
918
1036
|
const lines = [];
|
|
919
1037
|
lines.push(`${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {`);
|
|
920
|
-
|
|
921
|
-
|
|
1038
|
+
if (canSplit) {
|
|
1039
|
+
preamble.push(emitAssertObjectFunction());
|
|
1040
|
+
preamble.push(emitAssertFunction());
|
|
1041
|
+
lines.push(` if (!isObject(input)) ${assertObjectName}(input);`);
|
|
1042
|
+
if (!varsAfterGuard)
|
|
1043
|
+
lines.push(...varDeclLines);
|
|
1044
|
+
if (shallowGuard) {
|
|
1045
|
+
lines.push(` if (!(${shallowGuard})) ${assertName}(input);`);
|
|
1046
|
+
if (!exported && deepGuard !== null && fastPathNeedsKnownKeys) {
|
|
1047
|
+
const residual = [];
|
|
1048
|
+
for (let i = 0; i < fastPathChecks.length; i++) {
|
|
1049
|
+
const deep = fastPathChecks[i];
|
|
1050
|
+
const shallow = shallowChecks[i];
|
|
1051
|
+
if (deep === shallow)
|
|
1052
|
+
continue;
|
|
1053
|
+
residual.push(shallow !== void 0 && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
|
|
1054
|
+
}
|
|
1055
|
+
const returnInput = `return input as ${typeName};`;
|
|
1056
|
+
if (fastPathKeyCount === null) {
|
|
1057
|
+
lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) ${returnInput}` : ` ${returnInput}`);
|
|
1058
|
+
} else {
|
|
1059
|
+
const count = keyCountCheck(unknownKeys, fastPathKeyCount, residual.length === 0 ? " " : " ");
|
|
1060
|
+
if (count.statements.length === 0) {
|
|
1061
|
+
lines.push(` if (${[...residual, count.verdict].join(" && ")}) ${returnInput}`);
|
|
1062
|
+
} else if (residual.length === 0) {
|
|
1063
|
+
lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
|
|
1064
|
+
} else {
|
|
1065
|
+
lines.push(` if (${residual.join(" && ")}) {`);
|
|
1066
|
+
lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
|
|
1067
|
+
lines.push(` }`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const hoisted = [];
|
|
1072
|
+
const objectLines = buildObjectLines(true, hoisted);
|
|
1073
|
+
lines.push(...hoisted);
|
|
1074
|
+
emitReturn(lines, objectLines);
|
|
1075
|
+
} else {
|
|
1076
|
+
emitDeepGuardReturn(lines);
|
|
1077
|
+
if (varsAfterGuard)
|
|
1078
|
+
lines.push(...varDeclLines);
|
|
1079
|
+
lines.push(` ${assertName}(input);`);
|
|
1080
|
+
emitReturn(lines, buildObjectLines(true));
|
|
1081
|
+
}
|
|
1082
|
+
} else if (strict) {
|
|
922
1083
|
lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
|
|
923
1084
|
if (!varsAfterGuard)
|
|
924
1085
|
lines.push(...varDeclLines);
|
|
925
1086
|
lines.push(...warnLines);
|
|
926
|
-
const assertionLines = generateObjectStrictAssertion(schema, typeName, {
|
|
927
|
-
useRefImports,
|
|
928
|
-
suffix,
|
|
929
|
-
stripUnknown,
|
|
930
|
-
...rootSchema !== void 0 ? { rootSchema } : {}
|
|
931
|
-
}).slice(1);
|
|
932
1087
|
if (shallowGuard) {
|
|
933
1088
|
if (!exported && deepGuard && fastPathNeedsKnownKeys) {
|
|
934
1089
|
const residual = [];
|
|
@@ -940,34 +1095,41 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
940
1095
|
residual.push(shallow !== void 0 && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
|
|
941
1096
|
}
|
|
942
1097
|
lines.push(` if (${shallowGuard}) {`);
|
|
943
|
-
|
|
1098
|
+
const returnInput = `return input as ${typeName};`;
|
|
1099
|
+
if (fastPathKeyCount === null) {
|
|
1100
|
+
lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) ${returnInput}` : ` ${returnInput}`);
|
|
1101
|
+
} else {
|
|
1102
|
+
const count = keyCountCheck(unknownKeys, fastPathKeyCount, residual.length === 0 ? " " : " ");
|
|
1103
|
+
if (count.statements.length === 0) {
|
|
1104
|
+
lines.push(` if (${[...residual, count.verdict].join(" && ")}) ${returnInput}`);
|
|
1105
|
+
} else if (residual.length === 0) {
|
|
1106
|
+
lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
|
|
1107
|
+
} else {
|
|
1108
|
+
lines.push(` if (${residual.join(" && ")}) {`);
|
|
1109
|
+
lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
|
|
1110
|
+
lines.push(` }`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
944
1113
|
lines.push(` } else {`);
|
|
945
|
-
for (const assertionLine of assertionLines)
|
|
1114
|
+
for (const assertionLine of assertionLines.slice(1))
|
|
946
1115
|
lines.push(` ${assertionLine}`);
|
|
947
|
-
}
|
|
948
1116
|
lines.push(` }`);
|
|
949
1117
|
} else {
|
|
950
1118
|
lines.push(` if (!(${shallowGuard})) {`);
|
|
951
|
-
for (const assertionLine of assertionLines)
|
|
1119
|
+
for (const assertionLine of assertionLines.slice(1))
|
|
952
1120
|
lines.push(` ${assertionLine}`);
|
|
953
|
-
}
|
|
954
1121
|
lines.push(` }`);
|
|
955
1122
|
}
|
|
956
1123
|
emitReturn(lines, buildObjectLines(true));
|
|
957
1124
|
} else {
|
|
958
|
-
if (deepGuard) {
|
|
1125
|
+
if (deepGuard !== null) {
|
|
959
1126
|
emitDeepGuardReturn(lines);
|
|
960
1127
|
}
|
|
961
1128
|
if (varsAfterGuard)
|
|
962
1129
|
lines.push(...varDeclLines);
|
|
963
|
-
for (const assertionLine of assertionLines)
|
|
1130
|
+
for (const assertionLine of assertionLines.slice(1))
|
|
964
1131
|
lines.push(assertionLine);
|
|
965
|
-
|
|
966
|
-
if (rejectsUnknownKeys) {
|
|
967
|
-
lines.push(` for (const _k in input) {`);
|
|
968
|
-
lines.push(` if (${strictKeyCheck.isUnknown("_k")}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`);
|
|
969
|
-
lines.push(` }`);
|
|
970
|
-
}
|
|
1132
|
+
lines.push(...unknownKeyThrowLines());
|
|
971
1133
|
emitReturn(lines, buildObjectLines(true));
|
|
972
1134
|
}
|
|
973
1135
|
} else {
|
|
@@ -975,7 +1137,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
|
|
|
975
1137
|
if (!varsAfterGuard)
|
|
976
1138
|
lines.push(...varDeclLines);
|
|
977
1139
|
lines.push(...warnLines);
|
|
978
|
-
if (deepGuard) {
|
|
1140
|
+
if (deepGuard !== null) {
|
|
979
1141
|
emitDeepGuardReturn(lines);
|
|
980
1142
|
}
|
|
981
1143
|
if (varsAfterGuard)
|
|
@@ -1034,6 +1196,7 @@ ${loopLines.join("\n")}
|
|
|
1034
1196
|
};
|
|
1035
1197
|
const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown = false, caseInsensitive = false, context = {}) => {
|
|
1036
1198
|
const functionName = generateParserName(typeName);
|
|
1199
|
+
const unknownKeys = context.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
|
|
1037
1200
|
const entries = generatePropertyEntries(schema, useRefImports, suffix, caseInsensitive);
|
|
1038
1201
|
const propertyLines = entries.map((entry) => {
|
|
1039
1202
|
if (entry.isOptional) {
|
|
@@ -1042,7 +1205,7 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
|
|
|
1042
1205
|
return ` ${safeLiteralKey(entry.key)}: ${entry.value},`;
|
|
1043
1206
|
});
|
|
1044
1207
|
if (!isSchemaObject(schema) || !("patternProperties" in schema)) {
|
|
1045
|
-
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
|
|
1208
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
|
|
1046
1209
|
}
|
|
1047
1210
|
const patternProps = schema.patternProperties;
|
|
1048
1211
|
const patterns = Object.entries(patternProps);
|
|
@@ -1055,7 +1218,7 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
|
|
|
1055
1218
|
return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false, context);
|
|
1056
1219
|
}
|
|
1057
1220
|
if (!refPattern || !useRefImports) {
|
|
1058
|
-
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
|
|
1221
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
|
|
1059
1222
|
}
|
|
1060
1223
|
const [pattern, patternSchema] = refPattern;
|
|
1061
1224
|
const ref = patternSchema.$ref;
|
|
@@ -1231,7 +1394,7 @@ ${notObjectBranch}
|
|
|
1231
1394
|
return result;
|
|
1232
1395
|
};`;
|
|
1233
1396
|
};
|
|
1234
|
-
const generateConditionalParser = (schema, typeName, suffix, stripUnknown = false, caseInsensitive = false) => {
|
|
1397
|
+
const generateConditionalParser = (schema, typeName, suffix, stripUnknown = false, caseInsensitive = false, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
|
|
1235
1398
|
const functionName = generateParserName(typeName);
|
|
1236
1399
|
const ifSchema = schema.if;
|
|
1237
1400
|
const thenSchema = schema.then;
|
|
@@ -1241,7 +1404,7 @@ const generateConditionalParser = (schema, typeName, suffix, stripUnknown = fals
|
|
|
1241
1404
|
if (!thenHasRef || !elseHasRef) {
|
|
1242
1405
|
const mergedSchema = getConditionalObjectSchema(schema);
|
|
1243
1406
|
if (mergedSchema) {
|
|
1244
|
-
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
1407
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
|
|
1245
1408
|
}
|
|
1246
1409
|
return generateEmptyObjectParser(typeName);
|
|
1247
1410
|
}
|
|
@@ -1249,7 +1412,7 @@ const generateConditionalParser = (schema, typeName, suffix, stripUnknown = fals
|
|
|
1249
1412
|
if (!isRefCondition) {
|
|
1250
1413
|
const mergedSchema = getConditionalObjectSchema(schema);
|
|
1251
1414
|
if (mergedSchema) {
|
|
1252
|
-
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
1415
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
|
|
1253
1416
|
}
|
|
1254
1417
|
return generateEmptyObjectParser(typeName);
|
|
1255
1418
|
}
|
|
@@ -1333,11 +1496,13 @@ const selectParserStrategy = (schema, typeName, options) => {
|
|
|
1333
1496
|
const strict = options?.strict ?? false;
|
|
1334
1497
|
const stripUnknown = options?.stripUnknown ?? false;
|
|
1335
1498
|
const caseInsensitive = options?.caseInsensitive ?? false;
|
|
1499
|
+
const unknownKeys = options?.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
|
|
1336
1500
|
const suffix = options?.typeSuffix ?? "";
|
|
1337
1501
|
const context = {
|
|
1338
1502
|
useRefImports,
|
|
1339
1503
|
suffix,
|
|
1340
1504
|
stripUnknown,
|
|
1505
|
+
unknownKeys,
|
|
1341
1506
|
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {}
|
|
1342
1507
|
};
|
|
1343
1508
|
const copyLoopContext = { ...context, subParsers: false };
|
|
@@ -1352,6 +1517,7 @@ const selectParserStrategy = (schema, typeName, options) => {
|
|
|
1352
1517
|
stripUnknown,
|
|
1353
1518
|
caseInsensitive,
|
|
1354
1519
|
logWarnings,
|
|
1520
|
+
unknownKeys,
|
|
1355
1521
|
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1356
1522
|
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1357
1523
|
});
|
|
@@ -1366,12 +1532,13 @@ const selectParserStrategy = (schema, typeName, options) => {
|
|
|
1366
1532
|
stripUnknown,
|
|
1367
1533
|
caseInsensitive,
|
|
1368
1534
|
logWarnings,
|
|
1535
|
+
unknownKeys,
|
|
1369
1536
|
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1370
1537
|
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1371
1538
|
});
|
|
1372
1539
|
}
|
|
1373
1540
|
if (hasProperties(schema)) {
|
|
1374
|
-
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource);
|
|
1541
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource, unknownKeys);
|
|
1375
1542
|
}
|
|
1376
1543
|
if (strict && isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
|
|
1377
1544
|
return generateNonObjectParser(typeName, schema, strict, {
|
|
@@ -1380,16 +1547,17 @@ const selectParserStrategy = (schema, typeName, options) => {
|
|
|
1380
1547
|
stripUnknown,
|
|
1381
1548
|
caseInsensitive,
|
|
1382
1549
|
logWarnings,
|
|
1550
|
+
unknownKeys,
|
|
1383
1551
|
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1384
1552
|
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1385
1553
|
});
|
|
1386
1554
|
}
|
|
1387
1555
|
if (isSchemaObject(schema) && "if" in schema && "then" in schema && "else" in schema) {
|
|
1388
|
-
return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive);
|
|
1556
|
+
return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive, unknownKeys);
|
|
1389
1557
|
}
|
|
1390
1558
|
const conditionalObjectSchema = getConditionalObjectSchema(schema);
|
|
1391
1559
|
if (conditionalObjectSchema) {
|
|
1392
|
-
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
|
|
1560
|
+
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
|
|
1393
1561
|
}
|
|
1394
1562
|
if (!isObjectLikeSchema) {
|
|
1395
1563
|
return generateNonObjectParser(typeName, schema, strict, {
|
|
@@ -1398,6 +1566,7 @@ const selectParserStrategy = (schema, typeName, options) => {
|
|
|
1398
1566
|
stripUnknown,
|
|
1399
1567
|
caseInsensitive,
|
|
1400
1568
|
logWarnings,
|
|
1569
|
+
unknownKeys,
|
|
1401
1570
|
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1402
1571
|
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1403
1572
|
});
|
|
@@ -1430,7 +1599,7 @@ const generateParserFunction = (schema, typeName, rawOptions) => {
|
|
|
1430
1599
|
return selectParserStrategy(schema, typeName, options);
|
|
1431
1600
|
};
|
|
1432
1601
|
const shapeValidatorFunctionName = (typeName) => shapeValidatorName(typeName);
|
|
1433
|
-
const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", exported = true, stripUnknown = false, reservedNames = NO_RESERVED_NAMES) => {
|
|
1602
|
+
const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", exported = true, stripUnknown = false, reservedNames = NO_RESERVED_NAMES, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
|
|
1434
1603
|
const fnName = shapeValidatorName(typeName);
|
|
1435
1604
|
const exportPrefix = exported ? "export " : "";
|
|
1436
1605
|
const stub = `${exportPrefix}const ${fnName} = (_input: unknown): boolean => false;`;
|
|
@@ -1455,25 +1624,34 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
|
|
|
1455
1624
|
}
|
|
1456
1625
|
return stub;
|
|
1457
1626
|
}
|
|
1458
|
-
|
|
1627
|
+
const shape = shapeChecksOf(schema, typeName, "input", useRefImports, suffix, stripUnknown, reservedNames);
|
|
1628
|
+
if (shape === null)
|
|
1459
1629
|
return stub;
|
|
1630
|
+
const strictKeysGuard = shape.walksKeys ? `
|
|
1631
|
+
if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
|
|
1632
|
+
return renderShapeValidator(exportPrefix, fnName, shape.checks, shape.keyCount, strictKeysGuard, unknownKeys);
|
|
1633
|
+
};
|
|
1634
|
+
const shapeChecksOf = (schema, typeName, root, useRefImports, suffix, stripUnknown, reservedNames) => {
|
|
1635
|
+
if (!isSchemaObject(schema))
|
|
1636
|
+
return null;
|
|
1637
|
+
if (hasFastPathBlockingKeyword(schema))
|
|
1638
|
+
return null;
|
|
1639
|
+
if (!hasProperties(schema))
|
|
1640
|
+
return null;
|
|
1460
1641
|
if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema || "patternProperties" in schema || "if" in schema || "then" in schema || "else" in schema) {
|
|
1461
|
-
return
|
|
1642
|
+
return null;
|
|
1462
1643
|
}
|
|
1463
1644
|
const schemaProps = schema.properties;
|
|
1464
1645
|
const propertyKeys = Object.keys(schemaProps);
|
|
1465
1646
|
const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
|
|
1466
1647
|
const validatorStripKeys = hasStrictKeys(schema) || stripUnknown;
|
|
1467
|
-
const
|
|
1468
|
-
const strictKeysGuard = validatorStripKeys && validatorKeyCount === null ? `
|
|
1469
|
-
if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
|
|
1648
|
+
const keyCount = validatorStripKeys ? exactKeyCountOf(schema) : null;
|
|
1470
1649
|
const checks = [];
|
|
1471
1650
|
for (const key of propertyKeys) {
|
|
1472
1651
|
const propSchema = schemaProps[key];
|
|
1473
|
-
if (shouldUseRecordRefImport(propSchema, useRefImports))
|
|
1474
|
-
return
|
|
1475
|
-
|
|
1476
|
-
const accessor = safeAccessor("input", key);
|
|
1652
|
+
if (shouldUseRecordRefImport(propSchema, useRefImports))
|
|
1653
|
+
return null;
|
|
1654
|
+
const accessor = safeAccessor(root, key);
|
|
1477
1655
|
const isRequired = isPropertyRequired(key, schema);
|
|
1478
1656
|
const subName = subTypeNames.get(key);
|
|
1479
1657
|
let check = subName ? `${shapeValidatorName(subName)}(${accessor})` : generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
|
|
@@ -1481,9 +1659,8 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
|
|
|
1481
1659
|
if (check !== null && itemSubName) {
|
|
1482
1660
|
check = `${check} && _every${itemSubName}(${accessor})`;
|
|
1483
1661
|
}
|
|
1484
|
-
if (check === null)
|
|
1485
|
-
return
|
|
1486
|
-
}
|
|
1662
|
+
if (check === null)
|
|
1663
|
+
return null;
|
|
1487
1664
|
if (isRequired) {
|
|
1488
1665
|
checks.push(check);
|
|
1489
1666
|
} else {
|
|
@@ -1491,28 +1668,56 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
|
|
|
1491
1668
|
}
|
|
1492
1669
|
}
|
|
1493
1670
|
if (hasMinProperties(schema))
|
|
1494
|
-
checks.push(`Object.keys(
|
|
1671
|
+
checks.push(`Object.keys(${root}).length >= ${schema.minProperties}`);
|
|
1495
1672
|
if (hasMaxProperties(schema))
|
|
1496
|
-
checks.push(`Object.keys(
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1673
|
+
checks.push(`Object.keys(${root}).length <= ${schema.maxProperties}`);
|
|
1674
|
+
return { checks, keyCount, walksKeys: validatorStripKeys && keyCount === null, propertyCount: propertyKeys.length };
|
|
1675
|
+
};
|
|
1676
|
+
const inlineNestedShapeCheck = (childSchema, childTypeName, local, useRefImports, suffix, stripUnknown, reservedNames, unknownKeys) => {
|
|
1677
|
+
const readRoot = `(${local} as Record<string, any>)`;
|
|
1678
|
+
const shape = shapeChecksOf(childSchema, childTypeName, readRoot, useRefImports, suffix, stripUnknown, reservedNames);
|
|
1679
|
+
if (shape === null || shape.walksKeys)
|
|
1680
|
+
return null;
|
|
1681
|
+
if (shape.propertyCount === 0 || shape.propertyCount > INLINE_NESTED_MAX_PROPERTIES)
|
|
1682
|
+
return null;
|
|
1683
|
+
const count = shape.keyCount === null ? null : keyCountCheck(unknownKeys, shape.keyCount, "", local);
|
|
1684
|
+
if (count !== null && count.statements.length > 0)
|
|
1685
|
+
return null;
|
|
1686
|
+
const parts = [
|
|
1687
|
+
`typeof ${local} === "object"`,
|
|
1688
|
+
`${local} !== null`,
|
|
1689
|
+
`!Array.isArray(${local})`,
|
|
1690
|
+
...shape.checks,
|
|
1691
|
+
...count === null ? [] : [count.verdict]
|
|
1692
|
+
];
|
|
1693
|
+
return { check: `(${parts.join(" && ")})`, propertyCount: shape.propertyCount };
|
|
1694
|
+
};
|
|
1695
|
+
const renderShapeValidator = (exportPrefix, fnName, checks, keyCount, strictKeysGuard, unknownKeys) => {
|
|
1696
|
+
const head = `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
|
|
1697
|
+
if (!isObject(input)) return false;${strictKeysGuard}`;
|
|
1698
|
+
const chain = checks.join("\n && ");
|
|
1699
|
+
if (keyCount === null) {
|
|
1700
|
+
if (checks.length === 0) {
|
|
1701
|
+
return strictKeysGuard ? `${head}
|
|
1504
1702
|
return true;
|
|
1505
|
-
};`;
|
|
1703
|
+
};` : `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
|
|
1506
1704
|
}
|
|
1507
|
-
return `${
|
|
1705
|
+
return `${head}
|
|
1706
|
+
return ${chain};
|
|
1707
|
+
};`;
|
|
1508
1708
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1709
|
+
const count = keyCountCheck(unknownKeys, keyCount, " ");
|
|
1710
|
+
if (count.statements.length === 0) {
|
|
1711
|
+
return `${head}
|
|
1712
|
+
return ${checks.length === 0 ? count.verdict : `${chain}
|
|
1713
|
+
&& ${count.verdict}`};
|
|
1714
|
+
};`;
|
|
1512
1715
|
}
|
|
1513
|
-
|
|
1514
|
-
if (!
|
|
1515
|
-
return ${
|
|
1716
|
+
const gate = checks.length === 0 ? "" : `
|
|
1717
|
+
if (!(${chain})) return false;`;
|
|
1718
|
+
return `${head}${gate}
|
|
1719
|
+
${count.statements.join("\n")}
|
|
1720
|
+
return ${count.verdict};
|
|
1516
1721
|
};`;
|
|
1517
1722
|
};
|
|
1518
1723
|
export {
|