@formspec/build 0.1.0-alpha.14 → 0.1.0-alpha.16
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/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +22 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
- package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +30 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +11 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +5 -3
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +25 -9
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +546 -102
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +15 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +544 -102
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +170 -6
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +877 -128
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +876 -131
- package/dist/cli.js.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +850 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +847 -129
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +946 -187
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +944 -189
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +8 -2
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +27 -4
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/json-schema/types.d.ts +1 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -10
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/browser.cjs
CHANGED
|
@@ -25,7 +25,9 @@ __export(browser_exports, {
|
|
|
25
25
|
categorizationSchema: () => categorizationSchema,
|
|
26
26
|
categorySchema: () => categorySchema,
|
|
27
27
|
controlSchema: () => controlSchema,
|
|
28
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
28
29
|
generateJsonSchema: () => generateJsonSchema,
|
|
30
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
29
31
|
generateUiSchema: () => generateUiSchema,
|
|
30
32
|
getSchemaExtension: () => getSchemaExtension,
|
|
31
33
|
groupLayoutSchema: () => groupLayoutSchema,
|
|
@@ -225,7 +227,7 @@ function canonicalizeArrayField(field) {
|
|
|
225
227
|
const itemsType = {
|
|
226
228
|
kind: "object",
|
|
227
229
|
properties: itemProperties,
|
|
228
|
-
additionalProperties:
|
|
230
|
+
additionalProperties: true
|
|
229
231
|
};
|
|
230
232
|
const type = { kind: "array", items: itemsType };
|
|
231
233
|
const constraints = [];
|
|
@@ -260,7 +262,7 @@ function canonicalizeObjectField(field) {
|
|
|
260
262
|
const type = {
|
|
261
263
|
kind: "object",
|
|
262
264
|
properties,
|
|
263
|
-
additionalProperties:
|
|
265
|
+
additionalProperties: true
|
|
264
266
|
};
|
|
265
267
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
266
268
|
}
|
|
@@ -361,13 +363,26 @@ function buildObjectProperties(elements, insideConditional = false) {
|
|
|
361
363
|
var import_core2 = require("@formspec/core");
|
|
362
364
|
|
|
363
365
|
// src/json-schema/ir-generator.ts
|
|
364
|
-
function makeContext() {
|
|
365
|
-
|
|
366
|
+
function makeContext(options) {
|
|
367
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
368
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
defs: {},
|
|
375
|
+
extensionRegistry: options?.extensionRegistry,
|
|
376
|
+
vendorPrefix
|
|
377
|
+
};
|
|
366
378
|
}
|
|
367
|
-
function generateJsonSchemaFromIR(ir) {
|
|
368
|
-
const ctx = makeContext();
|
|
379
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
380
|
+
const ctx = makeContext(options);
|
|
369
381
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
370
382
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
383
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
384
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
385
|
+
}
|
|
371
386
|
}
|
|
372
387
|
const properties = {};
|
|
373
388
|
const required = [];
|
|
@@ -379,6 +394,9 @@ function generateJsonSchemaFromIR(ir) {
|
|
|
379
394
|
properties,
|
|
380
395
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
381
396
|
};
|
|
397
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
398
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
399
|
+
}
|
|
382
400
|
if (Object.keys(ctx.defs).length > 0) {
|
|
383
401
|
result.$defs = ctx.defs;
|
|
384
402
|
}
|
|
@@ -408,25 +426,54 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
408
426
|
}
|
|
409
427
|
function generateFieldSchema(field, ctx) {
|
|
410
428
|
const schema = generateTypeNode(field.type, ctx);
|
|
429
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
411
430
|
const directConstraints = [];
|
|
431
|
+
const itemConstraints = [];
|
|
412
432
|
const pathConstraints = [];
|
|
413
433
|
for (const c of field.constraints) {
|
|
414
434
|
if (c.path) {
|
|
415
435
|
pathConstraints.push(c);
|
|
436
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
437
|
+
itemConstraints.push(c);
|
|
416
438
|
} else {
|
|
417
439
|
directConstraints.push(c);
|
|
418
440
|
}
|
|
419
441
|
}
|
|
420
|
-
applyConstraints(schema, directConstraints);
|
|
421
|
-
|
|
442
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
443
|
+
if (itemStringSchema !== void 0) {
|
|
444
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
445
|
+
}
|
|
446
|
+
const rootAnnotations = [];
|
|
447
|
+
const itemAnnotations = [];
|
|
448
|
+
for (const annotation of field.annotations) {
|
|
449
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
450
|
+
itemAnnotations.push(annotation);
|
|
451
|
+
} else {
|
|
452
|
+
rootAnnotations.push(annotation);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
456
|
+
if (itemStringSchema !== void 0) {
|
|
457
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
458
|
+
}
|
|
422
459
|
if (pathConstraints.length === 0) {
|
|
423
460
|
return schema;
|
|
424
461
|
}
|
|
425
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
462
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
463
|
+
}
|
|
464
|
+
function isStringItemConstraint(constraint) {
|
|
465
|
+
switch (constraint.constraintKind) {
|
|
466
|
+
case "minLength":
|
|
467
|
+
case "maxLength":
|
|
468
|
+
case "pattern":
|
|
469
|
+
return true;
|
|
470
|
+
default:
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
426
473
|
}
|
|
427
|
-
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
474
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
428
475
|
if (schema.type === "array" && schema.items) {
|
|
429
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
476
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
430
477
|
return schema;
|
|
431
478
|
}
|
|
432
479
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -440,7 +487,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
440
487
|
const propertyOverrides = {};
|
|
441
488
|
for (const [target, constraints] of byTarget) {
|
|
442
489
|
const subSchema = {};
|
|
443
|
-
applyConstraints(subSchema, constraints);
|
|
490
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
444
491
|
propertyOverrides[target] = subSchema;
|
|
445
492
|
}
|
|
446
493
|
if (schema.$ref) {
|
|
@@ -484,6 +531,8 @@ function generateTypeNode(type, ctx) {
|
|
|
484
531
|
return generateArrayType(type, ctx);
|
|
485
532
|
case "object":
|
|
486
533
|
return generateObjectType(type, ctx);
|
|
534
|
+
case "record":
|
|
535
|
+
return generateRecordType(type, ctx);
|
|
487
536
|
case "union":
|
|
488
537
|
return generateUnionType(type, ctx);
|
|
489
538
|
case "reference":
|
|
@@ -491,7 +540,7 @@ function generateTypeNode(type, ctx) {
|
|
|
491
540
|
case "dynamic":
|
|
492
541
|
return generateDynamicType(type);
|
|
493
542
|
case "custom":
|
|
494
|
-
return generateCustomType(type);
|
|
543
|
+
return generateCustomType(type, ctx);
|
|
495
544
|
default: {
|
|
496
545
|
const _exhaustive = type;
|
|
497
546
|
return _exhaustive;
|
|
@@ -540,16 +589,27 @@ function generateObjectType(type, ctx) {
|
|
|
540
589
|
}
|
|
541
590
|
return schema;
|
|
542
591
|
}
|
|
592
|
+
function generateRecordType(type, ctx) {
|
|
593
|
+
return {
|
|
594
|
+
type: "object",
|
|
595
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
543
598
|
function generatePropertySchema(prop, ctx) {
|
|
544
599
|
const schema = generateTypeNode(prop.type, ctx);
|
|
545
|
-
applyConstraints(schema, prop.constraints);
|
|
546
|
-
applyAnnotations(schema, prop.annotations);
|
|
600
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
601
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
547
602
|
return schema;
|
|
548
603
|
}
|
|
549
604
|
function generateUnionType(type, ctx) {
|
|
550
605
|
if (isBooleanUnion(type)) {
|
|
551
606
|
return { type: "boolean" };
|
|
552
607
|
}
|
|
608
|
+
if (isNullableUnion(type)) {
|
|
609
|
+
return {
|
|
610
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
611
|
+
};
|
|
612
|
+
}
|
|
553
613
|
return {
|
|
554
614
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
555
615
|
};
|
|
@@ -559,6 +619,13 @@ function isBooleanUnion(type) {
|
|
|
559
619
|
const kinds = type.members.map((m) => m.kind);
|
|
560
620
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
561
621
|
}
|
|
622
|
+
function isNullableUnion(type) {
|
|
623
|
+
if (type.members.length !== 2) return false;
|
|
624
|
+
const nullCount = type.members.filter(
|
|
625
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
626
|
+
).length;
|
|
627
|
+
return nullCount === 1;
|
|
628
|
+
}
|
|
562
629
|
function generateReferenceType(type) {
|
|
563
630
|
return { $ref: `#/$defs/${type.name}` };
|
|
564
631
|
}
|
|
@@ -579,10 +646,7 @@ function generateDynamicType(type) {
|
|
|
579
646
|
"x-formspec-schemaSource": type.sourceKey
|
|
580
647
|
};
|
|
581
648
|
}
|
|
582
|
-
function
|
|
583
|
-
return { type: "object" };
|
|
584
|
-
}
|
|
585
|
-
function applyConstraints(schema, constraints) {
|
|
649
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
586
650
|
for (const constraint of constraints) {
|
|
587
651
|
switch (constraint.constraintKind) {
|
|
588
652
|
case "minimum":
|
|
@@ -624,9 +688,13 @@ function applyConstraints(schema, constraints) {
|
|
|
624
688
|
case "uniqueItems":
|
|
625
689
|
schema.uniqueItems = constraint.value;
|
|
626
690
|
break;
|
|
691
|
+
case "const":
|
|
692
|
+
schema.const = constraint.value;
|
|
693
|
+
break;
|
|
627
694
|
case "allowedMembers":
|
|
628
695
|
break;
|
|
629
696
|
case "custom":
|
|
697
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
630
698
|
break;
|
|
631
699
|
default: {
|
|
632
700
|
const _exhaustive = constraint;
|
|
@@ -635,7 +703,7 @@ function applyConstraints(schema, constraints) {
|
|
|
635
703
|
}
|
|
636
704
|
}
|
|
637
705
|
}
|
|
638
|
-
function applyAnnotations(schema, annotations) {
|
|
706
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
639
707
|
for (const annotation of annotations) {
|
|
640
708
|
switch (annotation.annotationKind) {
|
|
641
709
|
case "displayName":
|
|
@@ -647,14 +715,21 @@ function applyAnnotations(schema, annotations) {
|
|
|
647
715
|
case "defaultValue":
|
|
648
716
|
schema.default = annotation.value;
|
|
649
717
|
break;
|
|
718
|
+
case "format":
|
|
719
|
+
schema.format = annotation.value;
|
|
720
|
+
break;
|
|
650
721
|
case "deprecated":
|
|
651
722
|
schema.deprecated = true;
|
|
723
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
724
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
725
|
+
}
|
|
652
726
|
break;
|
|
653
727
|
case "placeholder":
|
|
654
728
|
break;
|
|
655
729
|
case "formatHint":
|
|
656
730
|
break;
|
|
657
731
|
case "custom":
|
|
732
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
658
733
|
break;
|
|
659
734
|
default: {
|
|
660
735
|
const _exhaustive = annotation;
|
|
@@ -663,11 +738,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
663
738
|
}
|
|
664
739
|
}
|
|
665
740
|
}
|
|
741
|
+
function generateCustomType(type, ctx) {
|
|
742
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
743
|
+
if (registration === void 0) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
749
|
+
}
|
|
750
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
751
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
752
|
+
if (registration === void 0) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
758
|
+
}
|
|
759
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
760
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
761
|
+
if (registration === void 0) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (registration.toJsonSchema === void 0) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
770
|
+
}
|
|
666
771
|
|
|
667
772
|
// src/json-schema/generator.ts
|
|
668
|
-
function generateJsonSchema(form) {
|
|
773
|
+
function generateJsonSchema(form, options) {
|
|
669
774
|
const ir = canonicalizeChainDSL(form);
|
|
670
|
-
return generateJsonSchemaFromIR(ir);
|
|
775
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
671
776
|
}
|
|
672
777
|
|
|
673
778
|
// src/ui-schema/schema.ts
|
|
@@ -805,25 +910,31 @@ function createShowRule(fieldName, value) {
|
|
|
805
910
|
}
|
|
806
911
|
};
|
|
807
912
|
}
|
|
913
|
+
function flattenConditionSchema(scope, schema) {
|
|
914
|
+
if (schema.allOf === void 0) {
|
|
915
|
+
if (scope === "#") {
|
|
916
|
+
return [schema];
|
|
917
|
+
}
|
|
918
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
919
|
+
return [
|
|
920
|
+
{
|
|
921
|
+
properties: {
|
|
922
|
+
[fieldName]: schema
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
];
|
|
926
|
+
}
|
|
927
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
928
|
+
}
|
|
808
929
|
function combineRules(parentRule, childRule) {
|
|
809
|
-
const parentCondition = parentRule.condition;
|
|
810
|
-
const childCondition = childRule.condition;
|
|
811
930
|
return {
|
|
812
931
|
effect: "SHOW",
|
|
813
932
|
condition: {
|
|
814
933
|
scope: "#",
|
|
815
934
|
schema: {
|
|
816
935
|
allOf: [
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
820
|
-
}
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
properties: {
|
|
824
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
825
|
-
}
|
|
826
|
-
}
|
|
936
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
937
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
827
938
|
]
|
|
828
939
|
}
|
|
829
940
|
}
|
|
@@ -831,10 +942,14 @@ function combineRules(parentRule, childRule) {
|
|
|
831
942
|
}
|
|
832
943
|
function fieldNodeToControl(field, parentRule) {
|
|
833
944
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
945
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
834
946
|
const control = {
|
|
835
947
|
type: "Control",
|
|
836
948
|
scope: fieldToScope(field.name),
|
|
837
949
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
950
|
+
...placeholderAnnotation !== void 0 && {
|
|
951
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
952
|
+
},
|
|
838
953
|
...parentRule !== void 0 && { rule: parentRule }
|
|
839
954
|
};
|
|
840
955
|
return control;
|
|
@@ -897,6 +1012,48 @@ function getSchemaExtension(schema, key) {
|
|
|
897
1012
|
return schema[key];
|
|
898
1013
|
}
|
|
899
1014
|
|
|
1015
|
+
// src/extensions/registry.ts
|
|
1016
|
+
function createExtensionRegistry(extensions) {
|
|
1017
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1018
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1019
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1020
|
+
for (const ext of extensions) {
|
|
1021
|
+
if (ext.types !== void 0) {
|
|
1022
|
+
for (const type of ext.types) {
|
|
1023
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1024
|
+
if (typeMap.has(qualifiedId)) {
|
|
1025
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1026
|
+
}
|
|
1027
|
+
typeMap.set(qualifiedId, type);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (ext.constraints !== void 0) {
|
|
1031
|
+
for (const constraint of ext.constraints) {
|
|
1032
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1033
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1034
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1035
|
+
}
|
|
1036
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (ext.annotations !== void 0) {
|
|
1040
|
+
for (const annotation of ext.annotations) {
|
|
1041
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1042
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1043
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1044
|
+
}
|
|
1045
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
extensions,
|
|
1051
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1052
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1053
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
900
1057
|
// src/json-schema/schema.ts
|
|
901
1058
|
var import_zod3 = require("zod");
|
|
902
1059
|
var jsonSchemaTypeSchema = import_zod3.z.enum([
|
|
@@ -960,12 +1117,9 @@ var jsonSchema7Schema = import_zod3.z.lazy(
|
|
|
960
1117
|
);
|
|
961
1118
|
|
|
962
1119
|
// src/validate/constraint-validator.ts
|
|
963
|
-
function makeCode(ctx, category, number) {
|
|
964
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
965
|
-
}
|
|
966
1120
|
function addContradiction(ctx, message, primary, related) {
|
|
967
1121
|
ctx.diagnostics.push({
|
|
968
|
-
code:
|
|
1122
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
969
1123
|
message,
|
|
970
1124
|
severity: "error",
|
|
971
1125
|
primaryLocation: primary,
|
|
@@ -974,7 +1128,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
974
1128
|
}
|
|
975
1129
|
function addTypeMismatch(ctx, message, primary) {
|
|
976
1130
|
ctx.diagnostics.push({
|
|
977
|
-
code:
|
|
1131
|
+
code: "TYPE_MISMATCH",
|
|
978
1132
|
message,
|
|
979
1133
|
severity: "error",
|
|
980
1134
|
primaryLocation: primary,
|
|
@@ -983,13 +1137,31 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
983
1137
|
}
|
|
984
1138
|
function addUnknownExtension(ctx, message, primary) {
|
|
985
1139
|
ctx.diagnostics.push({
|
|
986
|
-
code:
|
|
1140
|
+
code: "UNKNOWN_EXTENSION",
|
|
987
1141
|
message,
|
|
988
1142
|
severity: "warning",
|
|
989
1143
|
primaryLocation: primary,
|
|
990
1144
|
relatedLocations: []
|
|
991
1145
|
});
|
|
992
1146
|
}
|
|
1147
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
1148
|
+
ctx.diagnostics.push({
|
|
1149
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
1150
|
+
message,
|
|
1151
|
+
severity: "error",
|
|
1152
|
+
primaryLocation: primary,
|
|
1153
|
+
relatedLocations: []
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1157
|
+
ctx.diagnostics.push({
|
|
1158
|
+
code: "CONSTRAINT_BROADENING",
|
|
1159
|
+
message,
|
|
1160
|
+
severity: "error",
|
|
1161
|
+
primaryLocation: primary,
|
|
1162
|
+
relatedLocations: [related]
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
993
1165
|
function findNumeric(constraints, constraintKind) {
|
|
994
1166
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
995
1167
|
}
|
|
@@ -1001,6 +1173,165 @@ function findAllowedMembers(constraints) {
|
|
|
1001
1173
|
(c) => c.constraintKind === "allowedMembers"
|
|
1002
1174
|
);
|
|
1003
1175
|
}
|
|
1176
|
+
function findConstConstraints(constraints) {
|
|
1177
|
+
return constraints.filter(
|
|
1178
|
+
(c) => c.constraintKind === "const"
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
function jsonValueEquals(left, right) {
|
|
1182
|
+
if (left === right) {
|
|
1183
|
+
return true;
|
|
1184
|
+
}
|
|
1185
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1186
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1190
|
+
}
|
|
1191
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1192
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
const leftKeys = Object.keys(left).sort();
|
|
1196
|
+
const rightKeys = Object.keys(right).sort();
|
|
1197
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
return leftKeys.every((key, index) => {
|
|
1201
|
+
const rightKey = rightKeys[index];
|
|
1202
|
+
if (rightKey !== key) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
const leftValue = left[key];
|
|
1206
|
+
const rightValue = right[rightKey];
|
|
1207
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
function isJsonObject(value) {
|
|
1213
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1214
|
+
}
|
|
1215
|
+
function isOrderedBoundConstraint(constraint) {
|
|
1216
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
1217
|
+
}
|
|
1218
|
+
function pathKey(constraint) {
|
|
1219
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
1220
|
+
}
|
|
1221
|
+
function orderedBoundFamily(kind) {
|
|
1222
|
+
switch (kind) {
|
|
1223
|
+
case "minimum":
|
|
1224
|
+
case "exclusiveMinimum":
|
|
1225
|
+
return "numeric-lower";
|
|
1226
|
+
case "maximum":
|
|
1227
|
+
case "exclusiveMaximum":
|
|
1228
|
+
return "numeric-upper";
|
|
1229
|
+
case "minLength":
|
|
1230
|
+
return "minLength";
|
|
1231
|
+
case "minItems":
|
|
1232
|
+
return "minItems";
|
|
1233
|
+
case "maxLength":
|
|
1234
|
+
return "maxLength";
|
|
1235
|
+
case "maxItems":
|
|
1236
|
+
return "maxItems";
|
|
1237
|
+
default: {
|
|
1238
|
+
const _exhaustive = kind;
|
|
1239
|
+
return _exhaustive;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
function isNumericLowerKind(kind) {
|
|
1244
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
1245
|
+
}
|
|
1246
|
+
function isNumericUpperKind(kind) {
|
|
1247
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
1248
|
+
}
|
|
1249
|
+
function describeConstraintTag(constraint) {
|
|
1250
|
+
return `@${constraint.constraintKind}`;
|
|
1251
|
+
}
|
|
1252
|
+
function compareConstraintStrength(current, previous) {
|
|
1253
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
1254
|
+
if (family === "numeric-lower") {
|
|
1255
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
1256
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
1257
|
+
}
|
|
1258
|
+
if (current.value !== previous.value) {
|
|
1259
|
+
return current.value > previous.value ? 1 : -1;
|
|
1260
|
+
}
|
|
1261
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
1262
|
+
return 1;
|
|
1263
|
+
}
|
|
1264
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
1265
|
+
return -1;
|
|
1266
|
+
}
|
|
1267
|
+
return 0;
|
|
1268
|
+
}
|
|
1269
|
+
if (family === "numeric-upper") {
|
|
1270
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
1271
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
1272
|
+
}
|
|
1273
|
+
if (current.value !== previous.value) {
|
|
1274
|
+
return current.value < previous.value ? 1 : -1;
|
|
1275
|
+
}
|
|
1276
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
1277
|
+
return 1;
|
|
1278
|
+
}
|
|
1279
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
1280
|
+
return -1;
|
|
1281
|
+
}
|
|
1282
|
+
return 0;
|
|
1283
|
+
}
|
|
1284
|
+
switch (family) {
|
|
1285
|
+
case "minLength":
|
|
1286
|
+
case "minItems":
|
|
1287
|
+
if (current.value === previous.value) {
|
|
1288
|
+
return 0;
|
|
1289
|
+
}
|
|
1290
|
+
return current.value > previous.value ? 1 : -1;
|
|
1291
|
+
case "maxLength":
|
|
1292
|
+
case "maxItems":
|
|
1293
|
+
if (current.value === previous.value) {
|
|
1294
|
+
return 0;
|
|
1295
|
+
}
|
|
1296
|
+
return current.value < previous.value ? 1 : -1;
|
|
1297
|
+
default: {
|
|
1298
|
+
const _exhaustive = family;
|
|
1299
|
+
return _exhaustive;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
1304
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1305
|
+
for (const constraint of constraints) {
|
|
1306
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
1310
|
+
const previous = strongestByKey.get(key);
|
|
1311
|
+
if (previous === void 0) {
|
|
1312
|
+
strongestByKey.set(key, constraint);
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
1316
|
+
if (strength < 0) {
|
|
1317
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
1318
|
+
fieldName,
|
|
1319
|
+
constraint.path?.segments ?? []
|
|
1320
|
+
);
|
|
1321
|
+
addConstraintBroadening(
|
|
1322
|
+
ctx,
|
|
1323
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
1324
|
+
constraint.provenance,
|
|
1325
|
+
previous.provenance
|
|
1326
|
+
);
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
if (strength <= 0) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
strongestByKey.set(key, constraint);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1004
1335
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1005
1336
|
const min = findNumeric(constraints, "minimum");
|
|
1006
1337
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1087,6 +1418,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
1087
1418
|
}
|
|
1088
1419
|
}
|
|
1089
1420
|
}
|
|
1421
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1422
|
+
const constConstraints = findConstConstraints(constraints);
|
|
1423
|
+
if (constConstraints.length < 2) return;
|
|
1424
|
+
const first = constConstraints[0];
|
|
1425
|
+
if (first === void 0) return;
|
|
1426
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
1427
|
+
const current = constConstraints[i];
|
|
1428
|
+
if (current === void 0) continue;
|
|
1429
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
addContradiction(
|
|
1433
|
+
ctx,
|
|
1434
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1435
|
+
first.provenance,
|
|
1436
|
+
current.provenance
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1090
1440
|
function typeLabel(type) {
|
|
1091
1441
|
switch (type.kind) {
|
|
1092
1442
|
case "primitive":
|
|
@@ -1097,6 +1447,8 @@ function typeLabel(type) {
|
|
|
1097
1447
|
return "array";
|
|
1098
1448
|
case "object":
|
|
1099
1449
|
return "object";
|
|
1450
|
+
case "record":
|
|
1451
|
+
return "record";
|
|
1100
1452
|
case "union":
|
|
1101
1453
|
return "union";
|
|
1102
1454
|
case "reference":
|
|
@@ -1111,85 +1463,173 @@ function typeLabel(type) {
|
|
|
1111
1463
|
}
|
|
1112
1464
|
}
|
|
1113
1465
|
}
|
|
1114
|
-
function
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1466
|
+
function dereferenceType(ctx, type) {
|
|
1467
|
+
let current = type;
|
|
1468
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1469
|
+
while (current.kind === "reference") {
|
|
1470
|
+
if (seen.has(current.name)) {
|
|
1471
|
+
return current;
|
|
1472
|
+
}
|
|
1473
|
+
seen.add(current.name);
|
|
1474
|
+
const definition = ctx.typeRegistry[current.name];
|
|
1475
|
+
if (definition === void 0) {
|
|
1476
|
+
return current;
|
|
1477
|
+
}
|
|
1478
|
+
current = definition.type;
|
|
1479
|
+
}
|
|
1480
|
+
return current;
|
|
1481
|
+
}
|
|
1482
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
1483
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
1484
|
+
if (segments.length === 0) {
|
|
1485
|
+
return { kind: "resolved", type: effectiveType };
|
|
1486
|
+
}
|
|
1487
|
+
if (effectiveType.kind === "array") {
|
|
1488
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
1489
|
+
}
|
|
1490
|
+
if (effectiveType.kind === "object") {
|
|
1491
|
+
const [segment, ...rest] = segments;
|
|
1492
|
+
if (segment === void 0) {
|
|
1493
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
1494
|
+
}
|
|
1495
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
1496
|
+
if (property === void 0) {
|
|
1497
|
+
return { kind: "missing-property", segment };
|
|
1498
|
+
}
|
|
1499
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
1500
|
+
}
|
|
1501
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
1502
|
+
}
|
|
1503
|
+
function formatPathTargetFieldName(fieldName, path) {
|
|
1504
|
+
return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
|
|
1505
|
+
}
|
|
1506
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
1507
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
1508
|
+
const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
|
|
1509
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1510
|
+
const isArray = effectiveType.kind === "array";
|
|
1511
|
+
const isEnum = effectiveType.kind === "enum";
|
|
1512
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1513
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1514
|
+
const label = typeLabel(effectiveType);
|
|
1515
|
+
const ck = constraint.constraintKind;
|
|
1516
|
+
switch (ck) {
|
|
1517
|
+
case "minimum":
|
|
1518
|
+
case "maximum":
|
|
1519
|
+
case "exclusiveMinimum":
|
|
1520
|
+
case "exclusiveMaximum":
|
|
1521
|
+
case "multipleOf": {
|
|
1522
|
+
if (!isNumber) {
|
|
1124
1523
|
addTypeMismatch(
|
|
1125
1524
|
ctx,
|
|
1126
|
-
`Field "${fieldName}":
|
|
1525
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1127
1526
|
constraint.provenance
|
|
1128
1527
|
);
|
|
1129
1528
|
}
|
|
1130
|
-
|
|
1529
|
+
break;
|
|
1131
1530
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
ctx,
|
|
1142
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1143
|
-
constraint.provenance
|
|
1144
|
-
);
|
|
1145
|
-
}
|
|
1146
|
-
break;
|
|
1531
|
+
case "minLength":
|
|
1532
|
+
case "maxLength":
|
|
1533
|
+
case "pattern": {
|
|
1534
|
+
if (!isString && !isStringArray) {
|
|
1535
|
+
addTypeMismatch(
|
|
1536
|
+
ctx,
|
|
1537
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1538
|
+
constraint.provenance
|
|
1539
|
+
);
|
|
1147
1540
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1541
|
+
break;
|
|
1542
|
+
}
|
|
1543
|
+
case "minItems":
|
|
1544
|
+
case "maxItems":
|
|
1545
|
+
case "uniqueItems": {
|
|
1546
|
+
if (!isArray) {
|
|
1547
|
+
addTypeMismatch(
|
|
1548
|
+
ctx,
|
|
1549
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1550
|
+
constraint.provenance
|
|
1551
|
+
);
|
|
1159
1552
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
case "allowedMembers": {
|
|
1556
|
+
if (!isEnum) {
|
|
1557
|
+
addTypeMismatch(
|
|
1558
|
+
ctx,
|
|
1559
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1560
|
+
constraint.provenance
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
case "const": {
|
|
1566
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
1567
|
+
if (!isPrimitiveConstType) {
|
|
1568
|
+
addTypeMismatch(
|
|
1569
|
+
ctx,
|
|
1570
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1571
|
+
constraint.provenance
|
|
1572
|
+
);
|
|
1170
1573
|
break;
|
|
1171
1574
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1575
|
+
if (effectiveType.kind === "primitive") {
|
|
1576
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1577
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
1174
1578
|
addTypeMismatch(
|
|
1175
1579
|
ctx,
|
|
1176
|
-
`Field "${fieldName}":
|
|
1580
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
1177
1581
|
constraint.provenance
|
|
1178
1582
|
);
|
|
1179
1583
|
}
|
|
1180
1584
|
break;
|
|
1181
1585
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1586
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
1587
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
1588
|
+
addTypeMismatch(
|
|
1589
|
+
ctx,
|
|
1590
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
1591
|
+
constraint.provenance
|
|
1592
|
+
);
|
|
1185
1593
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
case "custom": {
|
|
1597
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
default: {
|
|
1601
|
+
const _exhaustive = constraint;
|
|
1602
|
+
throw new Error(
|
|
1603
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1609
|
+
for (const constraint of constraints) {
|
|
1610
|
+
if (constraint.path) {
|
|
1611
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1612
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1613
|
+
if (resolution.kind === "missing-property") {
|
|
1614
|
+
addUnknownPathTarget(
|
|
1615
|
+
ctx,
|
|
1616
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1617
|
+
constraint.provenance
|
|
1190
1618
|
);
|
|
1619
|
+
continue;
|
|
1191
1620
|
}
|
|
1621
|
+
if (resolution.kind === "unresolvable") {
|
|
1622
|
+
addTypeMismatch(
|
|
1623
|
+
ctx,
|
|
1624
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
1625
|
+
constraint.provenance
|
|
1626
|
+
);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1630
|
+
continue;
|
|
1192
1631
|
}
|
|
1632
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1193
1633
|
}
|
|
1194
1634
|
}
|
|
1195
1635
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -1233,6 +1673,8 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1233
1673
|
checkNumericContradictions(ctx, name, constraints);
|
|
1234
1674
|
checkLengthContradictions(ctx, name, constraints);
|
|
1235
1675
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1676
|
+
checkConstContradictions(ctx, name, constraints);
|
|
1677
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
1236
1678
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1237
1679
|
}
|
|
1238
1680
|
function validateElement(ctx, element) {
|
|
@@ -1259,8 +1701,8 @@ function validateElement(ctx, element) {
|
|
|
1259
1701
|
function validateIR(ir, options) {
|
|
1260
1702
|
const ctx = {
|
|
1261
1703
|
diagnostics: [],
|
|
1262
|
-
|
|
1263
|
-
|
|
1704
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1705
|
+
typeRegistry: ir.typeRegistry
|
|
1264
1706
|
};
|
|
1265
1707
|
for (const element of ir.elements) {
|
|
1266
1708
|
validateElement(ctx, element);
|
|
@@ -1272,9 +1714,9 @@ function validateIR(ir, options) {
|
|
|
1272
1714
|
}
|
|
1273
1715
|
|
|
1274
1716
|
// src/browser.ts
|
|
1275
|
-
function buildFormSchemas(form) {
|
|
1717
|
+
function buildFormSchemas(form, options) {
|
|
1276
1718
|
return {
|
|
1277
|
-
jsonSchema: generateJsonSchema(form),
|
|
1719
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1278
1720
|
uiSchema: generateUiSchema(form)
|
|
1279
1721
|
};
|
|
1280
1722
|
}
|
|
@@ -1285,7 +1727,9 @@ function buildFormSchemas(form) {
|
|
|
1285
1727
|
categorizationSchema,
|
|
1286
1728
|
categorySchema,
|
|
1287
1729
|
controlSchema,
|
|
1730
|
+
createExtensionRegistry,
|
|
1288
1731
|
generateJsonSchema,
|
|
1732
|
+
generateJsonSchemaFromIR,
|
|
1289
1733
|
generateUiSchema,
|
|
1290
1734
|
getSchemaExtension,
|
|
1291
1735
|
groupLayoutSchema,
|