@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.js
CHANGED
|
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
|
|
|
177
177
|
const itemsType = {
|
|
178
178
|
kind: "object",
|
|
179
179
|
properties: itemProperties,
|
|
180
|
-
additionalProperties:
|
|
180
|
+
additionalProperties: true
|
|
181
181
|
};
|
|
182
182
|
const type = { kind: "array", items: itemsType };
|
|
183
183
|
const constraints = [];
|
|
@@ -212,7 +212,7 @@ function canonicalizeObjectField(field) {
|
|
|
212
212
|
const type = {
|
|
213
213
|
kind: "object",
|
|
214
214
|
properties,
|
|
215
|
-
additionalProperties:
|
|
215
|
+
additionalProperties: true
|
|
216
216
|
};
|
|
217
217
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
218
218
|
}
|
|
@@ -313,13 +313,26 @@ function buildObjectProperties(elements, insideConditional = false) {
|
|
|
313
313
|
import { IR_VERSION as IR_VERSION2 } from "@formspec/core";
|
|
314
314
|
|
|
315
315
|
// src/json-schema/ir-generator.ts
|
|
316
|
-
function makeContext() {
|
|
317
|
-
|
|
316
|
+
function makeContext(options) {
|
|
317
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
318
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
defs: {},
|
|
325
|
+
extensionRegistry: options?.extensionRegistry,
|
|
326
|
+
vendorPrefix
|
|
327
|
+
};
|
|
318
328
|
}
|
|
319
|
-
function generateJsonSchemaFromIR(ir) {
|
|
320
|
-
const ctx = makeContext();
|
|
329
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
330
|
+
const ctx = makeContext(options);
|
|
321
331
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
322
332
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
333
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
334
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
335
|
+
}
|
|
323
336
|
}
|
|
324
337
|
const properties = {};
|
|
325
338
|
const required = [];
|
|
@@ -331,6 +344,9 @@ function generateJsonSchemaFromIR(ir) {
|
|
|
331
344
|
properties,
|
|
332
345
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
333
346
|
};
|
|
347
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
348
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
349
|
+
}
|
|
334
350
|
if (Object.keys(ctx.defs).length > 0) {
|
|
335
351
|
result.$defs = ctx.defs;
|
|
336
352
|
}
|
|
@@ -360,25 +376,54 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
360
376
|
}
|
|
361
377
|
function generateFieldSchema(field, ctx) {
|
|
362
378
|
const schema = generateTypeNode(field.type, ctx);
|
|
379
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
363
380
|
const directConstraints = [];
|
|
381
|
+
const itemConstraints = [];
|
|
364
382
|
const pathConstraints = [];
|
|
365
383
|
for (const c of field.constraints) {
|
|
366
384
|
if (c.path) {
|
|
367
385
|
pathConstraints.push(c);
|
|
386
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
387
|
+
itemConstraints.push(c);
|
|
368
388
|
} else {
|
|
369
389
|
directConstraints.push(c);
|
|
370
390
|
}
|
|
371
391
|
}
|
|
372
|
-
applyConstraints(schema, directConstraints);
|
|
373
|
-
|
|
392
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
393
|
+
if (itemStringSchema !== void 0) {
|
|
394
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
395
|
+
}
|
|
396
|
+
const rootAnnotations = [];
|
|
397
|
+
const itemAnnotations = [];
|
|
398
|
+
for (const annotation of field.annotations) {
|
|
399
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
400
|
+
itemAnnotations.push(annotation);
|
|
401
|
+
} else {
|
|
402
|
+
rootAnnotations.push(annotation);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
406
|
+
if (itemStringSchema !== void 0) {
|
|
407
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
408
|
+
}
|
|
374
409
|
if (pathConstraints.length === 0) {
|
|
375
410
|
return schema;
|
|
376
411
|
}
|
|
377
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
412
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
413
|
+
}
|
|
414
|
+
function isStringItemConstraint(constraint) {
|
|
415
|
+
switch (constraint.constraintKind) {
|
|
416
|
+
case "minLength":
|
|
417
|
+
case "maxLength":
|
|
418
|
+
case "pattern":
|
|
419
|
+
return true;
|
|
420
|
+
default:
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
378
423
|
}
|
|
379
|
-
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
424
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
380
425
|
if (schema.type === "array" && schema.items) {
|
|
381
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
426
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
382
427
|
return schema;
|
|
383
428
|
}
|
|
384
429
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -392,7 +437,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
392
437
|
const propertyOverrides = {};
|
|
393
438
|
for (const [target, constraints] of byTarget) {
|
|
394
439
|
const subSchema = {};
|
|
395
|
-
applyConstraints(subSchema, constraints);
|
|
440
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
396
441
|
propertyOverrides[target] = subSchema;
|
|
397
442
|
}
|
|
398
443
|
if (schema.$ref) {
|
|
@@ -436,6 +481,8 @@ function generateTypeNode(type, ctx) {
|
|
|
436
481
|
return generateArrayType(type, ctx);
|
|
437
482
|
case "object":
|
|
438
483
|
return generateObjectType(type, ctx);
|
|
484
|
+
case "record":
|
|
485
|
+
return generateRecordType(type, ctx);
|
|
439
486
|
case "union":
|
|
440
487
|
return generateUnionType(type, ctx);
|
|
441
488
|
case "reference":
|
|
@@ -443,7 +490,7 @@ function generateTypeNode(type, ctx) {
|
|
|
443
490
|
case "dynamic":
|
|
444
491
|
return generateDynamicType(type);
|
|
445
492
|
case "custom":
|
|
446
|
-
return generateCustomType(type);
|
|
493
|
+
return generateCustomType(type, ctx);
|
|
447
494
|
default: {
|
|
448
495
|
const _exhaustive = type;
|
|
449
496
|
return _exhaustive;
|
|
@@ -492,16 +539,27 @@ function generateObjectType(type, ctx) {
|
|
|
492
539
|
}
|
|
493
540
|
return schema;
|
|
494
541
|
}
|
|
542
|
+
function generateRecordType(type, ctx) {
|
|
543
|
+
return {
|
|
544
|
+
type: "object",
|
|
545
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
546
|
+
};
|
|
547
|
+
}
|
|
495
548
|
function generatePropertySchema(prop, ctx) {
|
|
496
549
|
const schema = generateTypeNode(prop.type, ctx);
|
|
497
|
-
applyConstraints(schema, prop.constraints);
|
|
498
|
-
applyAnnotations(schema, prop.annotations);
|
|
550
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
551
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
499
552
|
return schema;
|
|
500
553
|
}
|
|
501
554
|
function generateUnionType(type, ctx) {
|
|
502
555
|
if (isBooleanUnion(type)) {
|
|
503
556
|
return { type: "boolean" };
|
|
504
557
|
}
|
|
558
|
+
if (isNullableUnion(type)) {
|
|
559
|
+
return {
|
|
560
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
561
|
+
};
|
|
562
|
+
}
|
|
505
563
|
return {
|
|
506
564
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
507
565
|
};
|
|
@@ -511,6 +569,13 @@ function isBooleanUnion(type) {
|
|
|
511
569
|
const kinds = type.members.map((m) => m.kind);
|
|
512
570
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
513
571
|
}
|
|
572
|
+
function isNullableUnion(type) {
|
|
573
|
+
if (type.members.length !== 2) return false;
|
|
574
|
+
const nullCount = type.members.filter(
|
|
575
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
576
|
+
).length;
|
|
577
|
+
return nullCount === 1;
|
|
578
|
+
}
|
|
514
579
|
function generateReferenceType(type) {
|
|
515
580
|
return { $ref: `#/$defs/${type.name}` };
|
|
516
581
|
}
|
|
@@ -531,10 +596,7 @@ function generateDynamicType(type) {
|
|
|
531
596
|
"x-formspec-schemaSource": type.sourceKey
|
|
532
597
|
};
|
|
533
598
|
}
|
|
534
|
-
function
|
|
535
|
-
return { type: "object" };
|
|
536
|
-
}
|
|
537
|
-
function applyConstraints(schema, constraints) {
|
|
599
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
538
600
|
for (const constraint of constraints) {
|
|
539
601
|
switch (constraint.constraintKind) {
|
|
540
602
|
case "minimum":
|
|
@@ -576,9 +638,13 @@ function applyConstraints(schema, constraints) {
|
|
|
576
638
|
case "uniqueItems":
|
|
577
639
|
schema.uniqueItems = constraint.value;
|
|
578
640
|
break;
|
|
641
|
+
case "const":
|
|
642
|
+
schema.const = constraint.value;
|
|
643
|
+
break;
|
|
579
644
|
case "allowedMembers":
|
|
580
645
|
break;
|
|
581
646
|
case "custom":
|
|
647
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
582
648
|
break;
|
|
583
649
|
default: {
|
|
584
650
|
const _exhaustive = constraint;
|
|
@@ -587,7 +653,7 @@ function applyConstraints(schema, constraints) {
|
|
|
587
653
|
}
|
|
588
654
|
}
|
|
589
655
|
}
|
|
590
|
-
function applyAnnotations(schema, annotations) {
|
|
656
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
591
657
|
for (const annotation of annotations) {
|
|
592
658
|
switch (annotation.annotationKind) {
|
|
593
659
|
case "displayName":
|
|
@@ -599,14 +665,21 @@ function applyAnnotations(schema, annotations) {
|
|
|
599
665
|
case "defaultValue":
|
|
600
666
|
schema.default = annotation.value;
|
|
601
667
|
break;
|
|
668
|
+
case "format":
|
|
669
|
+
schema.format = annotation.value;
|
|
670
|
+
break;
|
|
602
671
|
case "deprecated":
|
|
603
672
|
schema.deprecated = true;
|
|
673
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
674
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
675
|
+
}
|
|
604
676
|
break;
|
|
605
677
|
case "placeholder":
|
|
606
678
|
break;
|
|
607
679
|
case "formatHint":
|
|
608
680
|
break;
|
|
609
681
|
case "custom":
|
|
682
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
610
683
|
break;
|
|
611
684
|
default: {
|
|
612
685
|
const _exhaustive = annotation;
|
|
@@ -615,11 +688,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
615
688
|
}
|
|
616
689
|
}
|
|
617
690
|
}
|
|
691
|
+
function generateCustomType(type, ctx) {
|
|
692
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
693
|
+
if (registration === void 0) {
|
|
694
|
+
throw new Error(
|
|
695
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
699
|
+
}
|
|
700
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
701
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
702
|
+
if (registration === void 0) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
708
|
+
}
|
|
709
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
710
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
711
|
+
if (registration === void 0) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (registration.toJsonSchema === void 0) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
720
|
+
}
|
|
618
721
|
|
|
619
722
|
// src/json-schema/generator.ts
|
|
620
|
-
function generateJsonSchema(form) {
|
|
723
|
+
function generateJsonSchema(form, options) {
|
|
621
724
|
const ir = canonicalizeChainDSL(form);
|
|
622
|
-
return generateJsonSchemaFromIR(ir);
|
|
725
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
623
726
|
}
|
|
624
727
|
|
|
625
728
|
// src/ui-schema/schema.ts
|
|
@@ -757,25 +860,31 @@ function createShowRule(fieldName, value) {
|
|
|
757
860
|
}
|
|
758
861
|
};
|
|
759
862
|
}
|
|
863
|
+
function flattenConditionSchema(scope, schema) {
|
|
864
|
+
if (schema.allOf === void 0) {
|
|
865
|
+
if (scope === "#") {
|
|
866
|
+
return [schema];
|
|
867
|
+
}
|
|
868
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
869
|
+
return [
|
|
870
|
+
{
|
|
871
|
+
properties: {
|
|
872
|
+
[fieldName]: schema
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
];
|
|
876
|
+
}
|
|
877
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
878
|
+
}
|
|
760
879
|
function combineRules(parentRule, childRule) {
|
|
761
|
-
const parentCondition = parentRule.condition;
|
|
762
|
-
const childCondition = childRule.condition;
|
|
763
880
|
return {
|
|
764
881
|
effect: "SHOW",
|
|
765
882
|
condition: {
|
|
766
883
|
scope: "#",
|
|
767
884
|
schema: {
|
|
768
885
|
allOf: [
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
772
|
-
}
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
properties: {
|
|
776
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
777
|
-
}
|
|
778
|
-
}
|
|
886
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
887
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
779
888
|
]
|
|
780
889
|
}
|
|
781
890
|
}
|
|
@@ -783,10 +892,14 @@ function combineRules(parentRule, childRule) {
|
|
|
783
892
|
}
|
|
784
893
|
function fieldNodeToControl(field, parentRule) {
|
|
785
894
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
895
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
786
896
|
const control = {
|
|
787
897
|
type: "Control",
|
|
788
898
|
scope: fieldToScope(field.name),
|
|
789
899
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
900
|
+
...placeholderAnnotation !== void 0 && {
|
|
901
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
902
|
+
},
|
|
790
903
|
...parentRule !== void 0 && { rule: parentRule }
|
|
791
904
|
};
|
|
792
905
|
return control;
|
|
@@ -849,6 +962,48 @@ function getSchemaExtension(schema, key) {
|
|
|
849
962
|
return schema[key];
|
|
850
963
|
}
|
|
851
964
|
|
|
965
|
+
// src/extensions/registry.ts
|
|
966
|
+
function createExtensionRegistry(extensions) {
|
|
967
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
968
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
969
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
970
|
+
for (const ext of extensions) {
|
|
971
|
+
if (ext.types !== void 0) {
|
|
972
|
+
for (const type of ext.types) {
|
|
973
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
974
|
+
if (typeMap.has(qualifiedId)) {
|
|
975
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
976
|
+
}
|
|
977
|
+
typeMap.set(qualifiedId, type);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (ext.constraints !== void 0) {
|
|
981
|
+
for (const constraint of ext.constraints) {
|
|
982
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
983
|
+
if (constraintMap.has(qualifiedId)) {
|
|
984
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
985
|
+
}
|
|
986
|
+
constraintMap.set(qualifiedId, constraint);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (ext.annotations !== void 0) {
|
|
990
|
+
for (const annotation of ext.annotations) {
|
|
991
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
992
|
+
if (annotationMap.has(qualifiedId)) {
|
|
993
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
994
|
+
}
|
|
995
|
+
annotationMap.set(qualifiedId, annotation);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
extensions,
|
|
1001
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1002
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1003
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
852
1007
|
// src/json-schema/schema.ts
|
|
853
1008
|
import { z as z3 } from "zod";
|
|
854
1009
|
var jsonSchemaTypeSchema = z3.enum([
|
|
@@ -912,12 +1067,9 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
912
1067
|
);
|
|
913
1068
|
|
|
914
1069
|
// src/validate/constraint-validator.ts
|
|
915
|
-
function makeCode(ctx, category, number) {
|
|
916
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
917
|
-
}
|
|
918
1070
|
function addContradiction(ctx, message, primary, related) {
|
|
919
1071
|
ctx.diagnostics.push({
|
|
920
|
-
code:
|
|
1072
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
921
1073
|
message,
|
|
922
1074
|
severity: "error",
|
|
923
1075
|
primaryLocation: primary,
|
|
@@ -926,7 +1078,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
926
1078
|
}
|
|
927
1079
|
function addTypeMismatch(ctx, message, primary) {
|
|
928
1080
|
ctx.diagnostics.push({
|
|
929
|
-
code:
|
|
1081
|
+
code: "TYPE_MISMATCH",
|
|
930
1082
|
message,
|
|
931
1083
|
severity: "error",
|
|
932
1084
|
primaryLocation: primary,
|
|
@@ -935,13 +1087,31 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
935
1087
|
}
|
|
936
1088
|
function addUnknownExtension(ctx, message, primary) {
|
|
937
1089
|
ctx.diagnostics.push({
|
|
938
|
-
code:
|
|
1090
|
+
code: "UNKNOWN_EXTENSION",
|
|
939
1091
|
message,
|
|
940
1092
|
severity: "warning",
|
|
941
1093
|
primaryLocation: primary,
|
|
942
1094
|
relatedLocations: []
|
|
943
1095
|
});
|
|
944
1096
|
}
|
|
1097
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
1098
|
+
ctx.diagnostics.push({
|
|
1099
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
1100
|
+
message,
|
|
1101
|
+
severity: "error",
|
|
1102
|
+
primaryLocation: primary,
|
|
1103
|
+
relatedLocations: []
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1107
|
+
ctx.diagnostics.push({
|
|
1108
|
+
code: "CONSTRAINT_BROADENING",
|
|
1109
|
+
message,
|
|
1110
|
+
severity: "error",
|
|
1111
|
+
primaryLocation: primary,
|
|
1112
|
+
relatedLocations: [related]
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
945
1115
|
function findNumeric(constraints, constraintKind) {
|
|
946
1116
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
947
1117
|
}
|
|
@@ -953,6 +1123,165 @@ function findAllowedMembers(constraints) {
|
|
|
953
1123
|
(c) => c.constraintKind === "allowedMembers"
|
|
954
1124
|
);
|
|
955
1125
|
}
|
|
1126
|
+
function findConstConstraints(constraints) {
|
|
1127
|
+
return constraints.filter(
|
|
1128
|
+
(c) => c.constraintKind === "const"
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
function jsonValueEquals(left, right) {
|
|
1132
|
+
if (left === right) {
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1136
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1140
|
+
}
|
|
1141
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1142
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
const leftKeys = Object.keys(left).sort();
|
|
1146
|
+
const rightKeys = Object.keys(right).sort();
|
|
1147
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
return leftKeys.every((key, index) => {
|
|
1151
|
+
const rightKey = rightKeys[index];
|
|
1152
|
+
if (rightKey !== key) {
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
const leftValue = left[key];
|
|
1156
|
+
const rightValue = right[rightKey];
|
|
1157
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
function isJsonObject(value) {
|
|
1163
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1164
|
+
}
|
|
1165
|
+
function isOrderedBoundConstraint(constraint) {
|
|
1166
|
+
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";
|
|
1167
|
+
}
|
|
1168
|
+
function pathKey(constraint) {
|
|
1169
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
1170
|
+
}
|
|
1171
|
+
function orderedBoundFamily(kind) {
|
|
1172
|
+
switch (kind) {
|
|
1173
|
+
case "minimum":
|
|
1174
|
+
case "exclusiveMinimum":
|
|
1175
|
+
return "numeric-lower";
|
|
1176
|
+
case "maximum":
|
|
1177
|
+
case "exclusiveMaximum":
|
|
1178
|
+
return "numeric-upper";
|
|
1179
|
+
case "minLength":
|
|
1180
|
+
return "minLength";
|
|
1181
|
+
case "minItems":
|
|
1182
|
+
return "minItems";
|
|
1183
|
+
case "maxLength":
|
|
1184
|
+
return "maxLength";
|
|
1185
|
+
case "maxItems":
|
|
1186
|
+
return "maxItems";
|
|
1187
|
+
default: {
|
|
1188
|
+
const _exhaustive = kind;
|
|
1189
|
+
return _exhaustive;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function isNumericLowerKind(kind) {
|
|
1194
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
1195
|
+
}
|
|
1196
|
+
function isNumericUpperKind(kind) {
|
|
1197
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
1198
|
+
}
|
|
1199
|
+
function describeConstraintTag(constraint) {
|
|
1200
|
+
return `@${constraint.constraintKind}`;
|
|
1201
|
+
}
|
|
1202
|
+
function compareConstraintStrength(current, previous) {
|
|
1203
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
1204
|
+
if (family === "numeric-lower") {
|
|
1205
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
1206
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
1207
|
+
}
|
|
1208
|
+
if (current.value !== previous.value) {
|
|
1209
|
+
return current.value > previous.value ? 1 : -1;
|
|
1210
|
+
}
|
|
1211
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
1212
|
+
return 1;
|
|
1213
|
+
}
|
|
1214
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
1215
|
+
return -1;
|
|
1216
|
+
}
|
|
1217
|
+
return 0;
|
|
1218
|
+
}
|
|
1219
|
+
if (family === "numeric-upper") {
|
|
1220
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
1221
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
1222
|
+
}
|
|
1223
|
+
if (current.value !== previous.value) {
|
|
1224
|
+
return current.value < previous.value ? 1 : -1;
|
|
1225
|
+
}
|
|
1226
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
1227
|
+
return 1;
|
|
1228
|
+
}
|
|
1229
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
1230
|
+
return -1;
|
|
1231
|
+
}
|
|
1232
|
+
return 0;
|
|
1233
|
+
}
|
|
1234
|
+
switch (family) {
|
|
1235
|
+
case "minLength":
|
|
1236
|
+
case "minItems":
|
|
1237
|
+
if (current.value === previous.value) {
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1240
|
+
return current.value > previous.value ? 1 : -1;
|
|
1241
|
+
case "maxLength":
|
|
1242
|
+
case "maxItems":
|
|
1243
|
+
if (current.value === previous.value) {
|
|
1244
|
+
return 0;
|
|
1245
|
+
}
|
|
1246
|
+
return current.value < previous.value ? 1 : -1;
|
|
1247
|
+
default: {
|
|
1248
|
+
const _exhaustive = family;
|
|
1249
|
+
return _exhaustive;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
1254
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1255
|
+
for (const constraint of constraints) {
|
|
1256
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
1260
|
+
const previous = strongestByKey.get(key);
|
|
1261
|
+
if (previous === void 0) {
|
|
1262
|
+
strongestByKey.set(key, constraint);
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
1266
|
+
if (strength < 0) {
|
|
1267
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
1268
|
+
fieldName,
|
|
1269
|
+
constraint.path?.segments ?? []
|
|
1270
|
+
);
|
|
1271
|
+
addConstraintBroadening(
|
|
1272
|
+
ctx,
|
|
1273
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
1274
|
+
constraint.provenance,
|
|
1275
|
+
previous.provenance
|
|
1276
|
+
);
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (strength <= 0) {
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
strongestByKey.set(key, constraint);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
956
1285
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
957
1286
|
const min = findNumeric(constraints, "minimum");
|
|
958
1287
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1039,6 +1368,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
1039
1368
|
}
|
|
1040
1369
|
}
|
|
1041
1370
|
}
|
|
1371
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1372
|
+
const constConstraints = findConstConstraints(constraints);
|
|
1373
|
+
if (constConstraints.length < 2) return;
|
|
1374
|
+
const first = constConstraints[0];
|
|
1375
|
+
if (first === void 0) return;
|
|
1376
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
1377
|
+
const current = constConstraints[i];
|
|
1378
|
+
if (current === void 0) continue;
|
|
1379
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
addContradiction(
|
|
1383
|
+
ctx,
|
|
1384
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1385
|
+
first.provenance,
|
|
1386
|
+
current.provenance
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1042
1390
|
function typeLabel(type) {
|
|
1043
1391
|
switch (type.kind) {
|
|
1044
1392
|
case "primitive":
|
|
@@ -1049,6 +1397,8 @@ function typeLabel(type) {
|
|
|
1049
1397
|
return "array";
|
|
1050
1398
|
case "object":
|
|
1051
1399
|
return "object";
|
|
1400
|
+
case "record":
|
|
1401
|
+
return "record";
|
|
1052
1402
|
case "union":
|
|
1053
1403
|
return "union";
|
|
1054
1404
|
case "reference":
|
|
@@ -1063,85 +1413,173 @@ function typeLabel(type) {
|
|
|
1063
1413
|
}
|
|
1064
1414
|
}
|
|
1065
1415
|
}
|
|
1066
|
-
function
|
|
1067
|
-
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1416
|
+
function dereferenceType(ctx, type) {
|
|
1417
|
+
let current = type;
|
|
1418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1419
|
+
while (current.kind === "reference") {
|
|
1420
|
+
if (seen.has(current.name)) {
|
|
1421
|
+
return current;
|
|
1422
|
+
}
|
|
1423
|
+
seen.add(current.name);
|
|
1424
|
+
const definition = ctx.typeRegistry[current.name];
|
|
1425
|
+
if (definition === void 0) {
|
|
1426
|
+
return current;
|
|
1427
|
+
}
|
|
1428
|
+
current = definition.type;
|
|
1429
|
+
}
|
|
1430
|
+
return current;
|
|
1431
|
+
}
|
|
1432
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
1433
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
1434
|
+
if (segments.length === 0) {
|
|
1435
|
+
return { kind: "resolved", type: effectiveType };
|
|
1436
|
+
}
|
|
1437
|
+
if (effectiveType.kind === "array") {
|
|
1438
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
1439
|
+
}
|
|
1440
|
+
if (effectiveType.kind === "object") {
|
|
1441
|
+
const [segment, ...rest] = segments;
|
|
1442
|
+
if (segment === void 0) {
|
|
1443
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
1444
|
+
}
|
|
1445
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
1446
|
+
if (property === void 0) {
|
|
1447
|
+
return { kind: "missing-property", segment };
|
|
1448
|
+
}
|
|
1449
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
1450
|
+
}
|
|
1451
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
1452
|
+
}
|
|
1453
|
+
function formatPathTargetFieldName(fieldName, path) {
|
|
1454
|
+
return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
|
|
1455
|
+
}
|
|
1456
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
1457
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
1458
|
+
const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
|
|
1459
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1460
|
+
const isArray = effectiveType.kind === "array";
|
|
1461
|
+
const isEnum = effectiveType.kind === "enum";
|
|
1462
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1463
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1464
|
+
const label = typeLabel(effectiveType);
|
|
1465
|
+
const ck = constraint.constraintKind;
|
|
1466
|
+
switch (ck) {
|
|
1467
|
+
case "minimum":
|
|
1468
|
+
case "maximum":
|
|
1469
|
+
case "exclusiveMinimum":
|
|
1470
|
+
case "exclusiveMaximum":
|
|
1471
|
+
case "multipleOf": {
|
|
1472
|
+
if (!isNumber) {
|
|
1076
1473
|
addTypeMismatch(
|
|
1077
1474
|
ctx,
|
|
1078
|
-
`Field "${fieldName}":
|
|
1475
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1079
1476
|
constraint.provenance
|
|
1080
1477
|
);
|
|
1081
1478
|
}
|
|
1082
|
-
|
|
1479
|
+
break;
|
|
1083
1480
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
ctx,
|
|
1094
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1095
|
-
constraint.provenance
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
break;
|
|
1481
|
+
case "minLength":
|
|
1482
|
+
case "maxLength":
|
|
1483
|
+
case "pattern": {
|
|
1484
|
+
if (!isString && !isStringArray) {
|
|
1485
|
+
addTypeMismatch(
|
|
1486
|
+
ctx,
|
|
1487
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1488
|
+
constraint.provenance
|
|
1489
|
+
);
|
|
1099
1490
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case "minItems":
|
|
1494
|
+
case "maxItems":
|
|
1495
|
+
case "uniqueItems": {
|
|
1496
|
+
if (!isArray) {
|
|
1497
|
+
addTypeMismatch(
|
|
1498
|
+
ctx,
|
|
1499
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1500
|
+
constraint.provenance
|
|
1501
|
+
);
|
|
1111
1502
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
case "allowedMembers": {
|
|
1506
|
+
if (!isEnum) {
|
|
1507
|
+
addTypeMismatch(
|
|
1508
|
+
ctx,
|
|
1509
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1510
|
+
constraint.provenance
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
case "const": {
|
|
1516
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
1517
|
+
if (!isPrimitiveConstType) {
|
|
1518
|
+
addTypeMismatch(
|
|
1519
|
+
ctx,
|
|
1520
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1521
|
+
constraint.provenance
|
|
1522
|
+
);
|
|
1122
1523
|
break;
|
|
1123
1524
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1525
|
+
if (effectiveType.kind === "primitive") {
|
|
1526
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1527
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
1126
1528
|
addTypeMismatch(
|
|
1127
1529
|
ctx,
|
|
1128
|
-
`Field "${fieldName}":
|
|
1530
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
1129
1531
|
constraint.provenance
|
|
1130
1532
|
);
|
|
1131
1533
|
}
|
|
1132
1534
|
break;
|
|
1133
1535
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1536
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
1537
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
1538
|
+
addTypeMismatch(
|
|
1539
|
+
ctx,
|
|
1540
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
1541
|
+
constraint.provenance
|
|
1542
|
+
);
|
|
1137
1543
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
case "custom": {
|
|
1547
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
default: {
|
|
1551
|
+
const _exhaustive = constraint;
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1559
|
+
for (const constraint of constraints) {
|
|
1560
|
+
if (constraint.path) {
|
|
1561
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1562
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1563
|
+
if (resolution.kind === "missing-property") {
|
|
1564
|
+
addUnknownPathTarget(
|
|
1565
|
+
ctx,
|
|
1566
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1567
|
+
constraint.provenance
|
|
1142
1568
|
);
|
|
1569
|
+
continue;
|
|
1143
1570
|
}
|
|
1571
|
+
if (resolution.kind === "unresolvable") {
|
|
1572
|
+
addTypeMismatch(
|
|
1573
|
+
ctx,
|
|
1574
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
1575
|
+
constraint.provenance
|
|
1576
|
+
);
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1580
|
+
continue;
|
|
1144
1581
|
}
|
|
1582
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1145
1583
|
}
|
|
1146
1584
|
}
|
|
1147
1585
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -1185,6 +1623,8 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1185
1623
|
checkNumericContradictions(ctx, name, constraints);
|
|
1186
1624
|
checkLengthContradictions(ctx, name, constraints);
|
|
1187
1625
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1626
|
+
checkConstContradictions(ctx, name, constraints);
|
|
1627
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
1188
1628
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1189
1629
|
}
|
|
1190
1630
|
function validateElement(ctx, element) {
|
|
@@ -1211,8 +1651,8 @@ function validateElement(ctx, element) {
|
|
|
1211
1651
|
function validateIR(ir, options) {
|
|
1212
1652
|
const ctx = {
|
|
1213
1653
|
diagnostics: [],
|
|
1214
|
-
|
|
1215
|
-
|
|
1654
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1655
|
+
typeRegistry: ir.typeRegistry
|
|
1216
1656
|
};
|
|
1217
1657
|
for (const element of ir.elements) {
|
|
1218
1658
|
validateElement(ctx, element);
|
|
@@ -1224,9 +1664,9 @@ function validateIR(ir, options) {
|
|
|
1224
1664
|
}
|
|
1225
1665
|
|
|
1226
1666
|
// src/browser.ts
|
|
1227
|
-
function buildFormSchemas(form) {
|
|
1667
|
+
function buildFormSchemas(form, options) {
|
|
1228
1668
|
return {
|
|
1229
|
-
jsonSchema: generateJsonSchema(form),
|
|
1669
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1230
1670
|
uiSchema: generateUiSchema(form)
|
|
1231
1671
|
};
|
|
1232
1672
|
}
|
|
@@ -1236,7 +1676,9 @@ export {
|
|
|
1236
1676
|
categorizationSchema,
|
|
1237
1677
|
categorySchema,
|
|
1238
1678
|
controlSchema,
|
|
1679
|
+
createExtensionRegistry,
|
|
1239
1680
|
generateJsonSchema,
|
|
1681
|
+
generateJsonSchemaFromIR,
|
|
1240
1682
|
generateUiSchema,
|
|
1241
1683
|
getSchemaExtension,
|
|
1242
1684
|
groupLayoutSchema,
|