@ng-forge/openapi-generator 0.9.0-next.9 → 0.9.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/bin/ng-forge-generator.js +266 -177
- package/package.json +3 -3
- package/src/generator/interface-generator.d.ts.map +1 -1
- package/src/index.js +260 -177
- package/src/mapper/schema-to-fields.d.ts +2 -0
- package/src/mapper/schema-to-fields.d.ts.map +1 -1
- package/src/parser/schema-walker.d.ts +1 -1
- package/src/parser/schema-walker.d.ts.map +1 -1
|
@@ -71,52 +71,60 @@ function formatEndpointLabel(endpoint) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// packages/openapi-generator/src/parser/schema-walker.ts
|
|
74
|
-
function walkSchema(schema, requiredFields = []) {
|
|
74
|
+
function walkSchema(schema, requiredFields = [], seen = /* @__PURE__ */ new WeakSet()) {
|
|
75
75
|
const warnings = [];
|
|
76
|
-
if (schema
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
if (schema.oneOf && schema.discriminator) {
|
|
80
|
-
return walkOneOfWithDiscriminator(
|
|
81
|
-
schema.oneOf,
|
|
82
|
-
schema.discriminator,
|
|
83
|
-
requiredFields,
|
|
84
|
-
warnings
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
if (schema.oneOf && !schema.discriminator) {
|
|
88
|
-
warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
|
|
89
|
-
return { properties: [], warnings };
|
|
90
|
-
}
|
|
91
|
-
if (schema.anyOf) {
|
|
92
|
-
warnings.push("anyOf schemas are not supported and were skipped");
|
|
93
|
-
return { properties: [], warnings };
|
|
76
|
+
if (seen.has(schema)) {
|
|
77
|
+
return { properties: [], warnings: ["Circular schema reference detected during allOf merge \u2014 nested schema skipped"] };
|
|
94
78
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
79
|
+
seen.add(schema);
|
|
80
|
+
try {
|
|
81
|
+
if (schema.allOf) {
|
|
82
|
+
return walkAllOf(schema.allOf, requiredFields, warnings, seen);
|
|
83
|
+
}
|
|
84
|
+
if (schema.oneOf && schema.discriminator) {
|
|
85
|
+
return walkOneOfWithDiscriminator(
|
|
86
|
+
schema.oneOf,
|
|
87
|
+
schema.discriminator,
|
|
88
|
+
requiredFields,
|
|
89
|
+
warnings
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (schema.oneOf && !schema.discriminator) {
|
|
93
|
+
warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
|
|
94
|
+
return { properties: [], warnings };
|
|
95
|
+
}
|
|
96
|
+
if (schema.anyOf) {
|
|
97
|
+
warnings.push("anyOf schemas are not supported and were skipped");
|
|
98
|
+
return { properties: [], warnings };
|
|
99
|
+
}
|
|
100
|
+
if ("if" in schema) {
|
|
101
|
+
warnings.push("if/then/else schemas are not supported and were skipped");
|
|
102
|
+
return { properties: [], warnings };
|
|
103
|
+
}
|
|
104
|
+
if (schema.additionalProperties && schema.additionalProperties !== false) {
|
|
105
|
+
warnings.push("additionalProperties are not supported and were skipped");
|
|
106
|
+
}
|
|
107
|
+
const properties = [];
|
|
108
|
+
const required = new Set(schema.required ?? requiredFields);
|
|
109
|
+
for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
|
|
110
|
+
if (isReferenceObject(propSchema)) continue;
|
|
111
|
+
properties.push({
|
|
112
|
+
name,
|
|
113
|
+
schema: propSchema,
|
|
114
|
+
required: required.has(name)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return { properties, warnings };
|
|
118
|
+
} finally {
|
|
119
|
+
seen.delete(schema);
|
|
111
120
|
}
|
|
112
|
-
return { properties, warnings };
|
|
113
121
|
}
|
|
114
|
-
function walkAllOf(schemas, requiredFields, warnings) {
|
|
122
|
+
function walkAllOf(schemas, requiredFields, warnings, seen) {
|
|
115
123
|
const mergedProperties = /* @__PURE__ */ new Map();
|
|
116
124
|
const allRequired = new Set(requiredFields);
|
|
117
125
|
for (const schema of schemas) {
|
|
118
126
|
if (isReferenceObject(schema)) continue;
|
|
119
|
-
const walked = walkSchema(schema, []);
|
|
127
|
+
const walked = walkSchema(schema, [], seen);
|
|
120
128
|
warnings.push(...walked.warnings);
|
|
121
129
|
for (const prop of walked.properties) {
|
|
122
130
|
mergedProperties.set(prop.name, prop);
|
|
@@ -453,7 +461,17 @@ var logger = {
|
|
|
453
461
|
};
|
|
454
462
|
|
|
455
463
|
// packages/openapi-generator/src/mapper/schema-to-fields.ts
|
|
456
|
-
var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
464
|
+
var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
465
|
+
"input",
|
|
466
|
+
"textarea",
|
|
467
|
+
"select",
|
|
468
|
+
"radio",
|
|
469
|
+
"multi-checkbox",
|
|
470
|
+
"slider",
|
|
471
|
+
"datepicker",
|
|
472
|
+
"checkbox",
|
|
473
|
+
"toggle"
|
|
474
|
+
]);
|
|
457
475
|
var CONTAINER_FIELD_TYPES = /* @__PURE__ */ new Set(["group", "array", "row", "page", "container"]);
|
|
458
476
|
function singularize(label) {
|
|
459
477
|
if (label.length >= 4 && label.endsWith("s") && !label.endsWith("ss")) {
|
|
@@ -462,54 +480,76 @@ function singularize(label) {
|
|
|
462
480
|
return label;
|
|
463
481
|
}
|
|
464
482
|
function mapSchemaToFields(schema, requiredFields, options = {}) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
483
|
+
return mapSchemaToFieldsInternal(schema, requiredFields, options, /* @__PURE__ */ new WeakSet());
|
|
484
|
+
}
|
|
485
|
+
function mapSchemaToFieldsInternal(schema, requiredFields, options, seen) {
|
|
486
|
+
if (seen.has(schema)) {
|
|
487
|
+
return {
|
|
488
|
+
fields: [],
|
|
489
|
+
ambiguousFields: [],
|
|
490
|
+
warnings: [
|
|
491
|
+
`Circular schema reference at '${options.schemaName ?? "<root>"}' \u2014 nested fields skipped. Use x-ng-forge-type to override how this field renders.`
|
|
492
|
+
]
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
seen.add(schema);
|
|
496
|
+
try {
|
|
497
|
+
const walked = walkSchema(schema, requiredFields);
|
|
498
|
+
const ambiguousFields = [];
|
|
499
|
+
const warnings = [...walked.warnings];
|
|
500
|
+
if (walked.discriminator) {
|
|
501
|
+
const discConfig = mapDiscriminator(walked.discriminator);
|
|
502
|
+
const fields2 = [discConfig.discriminatorField];
|
|
503
|
+
for (const group of discConfig.conditionalGroups) {
|
|
504
|
+
const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
|
|
505
|
+
if (variantSchema) {
|
|
506
|
+
const variantResult = mapSchemaToFieldsInternal(
|
|
507
|
+
variantSchema,
|
|
508
|
+
variantSchema.required ?? [],
|
|
509
|
+
{
|
|
510
|
+
...options,
|
|
511
|
+
schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
|
|
512
|
+
},
|
|
513
|
+
seen
|
|
514
|
+
);
|
|
515
|
+
const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
|
|
516
|
+
ambiguousFields.push(...variantResult.ambiguousFields);
|
|
517
|
+
warnings.push(...variantResult.warnings);
|
|
518
|
+
if (variantFields.length > 0) {
|
|
519
|
+
fields2.push({
|
|
520
|
+
key: `${group.discriminatorValue}Variant`,
|
|
521
|
+
type: "group",
|
|
522
|
+
fields: variantFields,
|
|
523
|
+
logic: [
|
|
524
|
+
{
|
|
525
|
+
type: "hidden",
|
|
526
|
+
condition: {
|
|
527
|
+
type: "fieldValue",
|
|
528
|
+
fieldPath: walked.discriminator.propertyName,
|
|
529
|
+
operator: "notEquals",
|
|
530
|
+
value: group.discriminatorValue
|
|
531
|
+
}
|
|
494
532
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
533
|
+
]
|
|
534
|
+
});
|
|
535
|
+
}
|
|
498
536
|
}
|
|
499
537
|
}
|
|
538
|
+
return { fields: fields2, ambiguousFields, warnings };
|
|
500
539
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
fields.push(field);
|
|
540
|
+
const fields = [];
|
|
541
|
+
for (const prop of walked.properties) {
|
|
542
|
+
const field = mapPropertyToField(prop, options, seen, ambiguousFields, warnings);
|
|
543
|
+
if (field) {
|
|
544
|
+
fields.push(field);
|
|
545
|
+
}
|
|
508
546
|
}
|
|
547
|
+
return { fields, ambiguousFields, warnings };
|
|
548
|
+
} finally {
|
|
549
|
+
seen.delete(schema);
|
|
509
550
|
}
|
|
510
|
-
return { fields, ambiguousFields, warnings };
|
|
511
551
|
}
|
|
512
|
-
function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
552
|
+
function mapPropertyToField(prop, options, seen, ambiguousFields, warnings) {
|
|
513
553
|
if (prop.schema["deprecated"]) {
|
|
514
554
|
warnings.push(`Property '${prop.name}' is deprecated and was skipped`);
|
|
515
555
|
return void 0;
|
|
@@ -517,10 +557,12 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
517
557
|
if (prop.schema.oneOf && prop.schema["discriminator"]) {
|
|
518
558
|
const schemaPrefix2 = options.schemaName ?? "";
|
|
519
559
|
const nestedSchemaName = schemaPrefix2 ? `${schemaPrefix2}.${prop.name}` : prop.name;
|
|
520
|
-
const innerResult =
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
560
|
+
const innerResult = mapSchemaToFieldsInternal(
|
|
561
|
+
prop.schema,
|
|
562
|
+
prop.schema.required ?? [],
|
|
563
|
+
{ ...options, schemaName: nestedSchemaName },
|
|
564
|
+
seen
|
|
565
|
+
);
|
|
524
566
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
525
567
|
warnings.push(...innerResult.warnings);
|
|
526
568
|
return {
|
|
@@ -550,19 +592,19 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
550
592
|
}
|
|
551
593
|
}
|
|
552
594
|
const validators = mapSchemaToValidators(prop.schema, prop.required);
|
|
595
|
+
const field = {
|
|
596
|
+
key: prop.name,
|
|
597
|
+
type: finalType
|
|
598
|
+
};
|
|
553
599
|
const arraySchema = prop.schema;
|
|
554
|
-
if (
|
|
600
|
+
if (finalType === "array") {
|
|
555
601
|
if (arraySchema["minItems"] !== void 0) {
|
|
556
|
-
|
|
602
|
+
field.minLength = arraySchema["minItems"];
|
|
557
603
|
}
|
|
558
604
|
if (arraySchema["maxItems"] !== void 0) {
|
|
559
|
-
|
|
605
|
+
field.maxLength = arraySchema["maxItems"];
|
|
560
606
|
}
|
|
561
607
|
}
|
|
562
|
-
const field = {
|
|
563
|
-
key: prop.name,
|
|
564
|
-
type: finalType
|
|
565
|
-
};
|
|
566
608
|
if (!CONTAINER_FIELD_TYPES.has(finalType)) {
|
|
567
609
|
field.label = prop.schema["title"] ?? toLabel(prop.name);
|
|
568
610
|
}
|
|
@@ -606,7 +648,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
606
648
|
}
|
|
607
649
|
}
|
|
608
650
|
if (validators.length > 0) {
|
|
609
|
-
|
|
651
|
+
if (CONTAINER_FIELD_TYPES.has(finalType)) {
|
|
652
|
+
const droppedTypes = validators.map((v) => v.type).join(", ");
|
|
653
|
+
const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
|
|
654
|
+
logger.verbose(
|
|
655
|
+
`Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
|
|
656
|
+
);
|
|
657
|
+
} else {
|
|
658
|
+
field.validators = validators;
|
|
659
|
+
}
|
|
610
660
|
}
|
|
611
661
|
if (options.editable === false) {
|
|
612
662
|
field.disabled = true;
|
|
@@ -633,14 +683,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
633
683
|
const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
|
|
634
684
|
const nestedOptions = { ...options, schemaName: nestedSchemaName };
|
|
635
685
|
if (typeResult.fieldType === "group" && prop.schema.properties) {
|
|
636
|
-
const innerResult =
|
|
686
|
+
const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
|
|
637
687
|
field.fields = innerResult.fields;
|
|
638
688
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
639
689
|
} else if (typeResult.fieldType === "array" && prop.schema["items"]) {
|
|
640
690
|
const items = prop.schema["items"];
|
|
641
691
|
const fieldLabel = singularize(toLabel(prop.name));
|
|
642
692
|
if (items.type === "object" || items.properties) {
|
|
643
|
-
const innerResult =
|
|
693
|
+
const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
|
|
644
694
|
field.template = innerResult.fields;
|
|
645
695
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
646
696
|
warnings.push(...innerResult.warnings);
|
|
@@ -740,6 +790,12 @@ function generateFieldLines(field, indent) {
|
|
|
740
790
|
}
|
|
741
791
|
lines.push(`${indent} ],`);
|
|
742
792
|
}
|
|
793
|
+
if (field.minLength !== void 0) {
|
|
794
|
+
lines.push(`${indent} minLength: ${field.minLength},`);
|
|
795
|
+
}
|
|
796
|
+
if (field.maxLength !== void 0) {
|
|
797
|
+
lines.push(`${indent} maxLength: ${field.maxLength},`);
|
|
798
|
+
}
|
|
743
799
|
if (field.disabled) {
|
|
744
800
|
lines.push(`${indent} disabled: true,`);
|
|
745
801
|
}
|
|
@@ -828,15 +884,21 @@ function generateInterface(schema, options) {
|
|
|
828
884
|
const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
|
|
829
885
|
const lines = [];
|
|
830
886
|
const nestedInterfaces = [];
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
887
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
888
|
+
seen.add(schema);
|
|
889
|
+
try {
|
|
890
|
+
const { properties, required } = resolveSchemaProperties(schema, seen);
|
|
891
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
892
|
+
for (const [name, propSchema] of properties) {
|
|
893
|
+
if (isReferenceObject(propSchema)) continue;
|
|
894
|
+
const optional = required.has(name) ? "" : "?";
|
|
895
|
+
const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
|
|
896
|
+
lines.push(` ${name}${optional}: ${tsType};`);
|
|
897
|
+
}
|
|
898
|
+
lines.push(`}`);
|
|
899
|
+
} finally {
|
|
900
|
+
seen.delete(schema);
|
|
838
901
|
}
|
|
839
|
-
lines.push(`}`);
|
|
840
902
|
if (nestedInterfaces.length > 0) {
|
|
841
903
|
return `// @generated by @ng-forge/openapi-generator
|
|
842
904
|
` + [...nestedInterfaces, "", ...lines, ""].join("\n");
|
|
@@ -845,18 +907,24 @@ function generateInterface(schema, options) {
|
|
|
845
907
|
return `// @generated by @ng-forge/openapi-generator
|
|
846
908
|
` + lines.join("\n");
|
|
847
909
|
}
|
|
848
|
-
function resolveSchemaProperties(schema) {
|
|
910
|
+
function resolveSchemaProperties(schema, seen) {
|
|
849
911
|
if (schema.allOf) {
|
|
850
912
|
const merged = /* @__PURE__ */ new Map();
|
|
851
913
|
const allRequired = new Set(schema.required ?? []);
|
|
852
914
|
for (const sub of schema.allOf) {
|
|
853
915
|
if (isReferenceObject(sub)) continue;
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
916
|
+
if (seen.has(sub)) continue;
|
|
917
|
+
seen.add(sub);
|
|
918
|
+
try {
|
|
919
|
+
const resolved = resolveSchemaProperties(sub, seen);
|
|
920
|
+
for (const [name, propSchema] of resolved.properties) {
|
|
921
|
+
merged.set(name, propSchema);
|
|
922
|
+
}
|
|
923
|
+
for (const r of resolved.required) {
|
|
924
|
+
allRequired.add(r);
|
|
925
|
+
}
|
|
926
|
+
} finally {
|
|
927
|
+
seen.delete(sub);
|
|
860
928
|
}
|
|
861
929
|
}
|
|
862
930
|
return { properties: Array.from(merged.entries()), required: allRequired };
|
|
@@ -868,7 +936,14 @@ function resolveSchemaProperties(schema) {
|
|
|
868
936
|
const discriminatorValues = [];
|
|
869
937
|
for (const variant of schema.oneOf) {
|
|
870
938
|
if (isReferenceObject(variant)) continue;
|
|
871
|
-
|
|
939
|
+
if (seen.has(variant)) continue;
|
|
940
|
+
seen.add(variant);
|
|
941
|
+
let resolved;
|
|
942
|
+
try {
|
|
943
|
+
resolved = resolveSchemaProperties(variant, seen);
|
|
944
|
+
} finally {
|
|
945
|
+
seen.delete(variant);
|
|
946
|
+
}
|
|
872
947
|
for (const [name, propSchema] of resolved.properties) {
|
|
873
948
|
if (name === discriminatorName) {
|
|
874
949
|
if (propSchema.enum) {
|
|
@@ -914,78 +989,86 @@ function resolveSchemaProperties(schema) {
|
|
|
914
989
|
}
|
|
915
990
|
return { properties, required };
|
|
916
991
|
}
|
|
917
|
-
function schemaToTsType(propertyName, schema, parentName, nestedInterfaces) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
type
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
992
|
+
function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
|
|
993
|
+
if (seen.has(schema)) {
|
|
994
|
+
return "unknown";
|
|
995
|
+
}
|
|
996
|
+
seen.add(schema);
|
|
997
|
+
try {
|
|
998
|
+
const rawType = schema["type"];
|
|
999
|
+
let type;
|
|
1000
|
+
let nullableFrom31Type = false;
|
|
1001
|
+
if (Array.isArray(rawType)) {
|
|
1002
|
+
const nonNull = rawType.filter((t) => t !== "null");
|
|
1003
|
+
nullableFrom31Type = nonNull.length !== rawType.length;
|
|
1004
|
+
type = nonNull[0];
|
|
1005
|
+
} else {
|
|
1006
|
+
type = rawType;
|
|
1007
|
+
}
|
|
1008
|
+
const isNullable = schema["nullable"] === true || nullableFrom31Type;
|
|
1009
|
+
const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
|
|
1010
|
+
if (enumValues && enumValues.length > 0) {
|
|
1011
|
+
const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
1012
|
+
return isNullable ? `${enumUnion} | null` : enumUnion;
|
|
1013
|
+
}
|
|
1014
|
+
if (isNullable) {
|
|
1015
|
+
const baseSchema = { ...schema, nullable: void 0, type };
|
|
1016
|
+
const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
|
|
1017
|
+
return `${baseType} | null`;
|
|
1018
|
+
}
|
|
1019
|
+
const unionSchemas = schema.oneOf ?? schema.anyOf;
|
|
1020
|
+
if (unionSchemas) {
|
|
1021
|
+
const disc = schema["discriminator"];
|
|
1022
|
+
const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
|
|
1023
|
+
const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
|
|
1024
|
+
if ((s.type === "object" || s.properties) && mappingKeys) {
|
|
1025
|
+
const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
|
|
1026
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
|
|
1027
|
+
const nestedInterface = generateNestedInterface(nestedName, s, seen);
|
|
1028
|
+
nestedInterfaces.push(nestedInterface);
|
|
1029
|
+
return nestedName;
|
|
1030
|
+
}
|
|
1031
|
+
return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
|
|
1032
|
+
});
|
|
1033
|
+
return types.join(" | ");
|
|
1034
|
+
}
|
|
1035
|
+
if (schema.allOf) {
|
|
1036
|
+
const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
|
|
1037
|
+
const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
|
|
1038
|
+
return t.includes(" | ") ? `(${t})` : t;
|
|
1039
|
+
});
|
|
1040
|
+
return types.join(" & ");
|
|
1041
|
+
}
|
|
1042
|
+
if (type === "null") return "null";
|
|
1043
|
+
if (type === "string") return "string";
|
|
1044
|
+
if (type === "integer" || type === "number") return "number";
|
|
1045
|
+
if (type === "boolean") return "boolean";
|
|
1046
|
+
const arrayItems = schema["items"];
|
|
1047
|
+
if (type === "array" && arrayItems) {
|
|
1048
|
+
const items = arrayItems;
|
|
1049
|
+
if (items.type === "object" || items.properties) {
|
|
1050
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
|
|
1051
|
+
const nestedInterface = generateNestedInterface(nestedName, items, seen);
|
|
948
1052
|
nestedInterfaces.push(nestedInterface);
|
|
949
|
-
return nestedName
|
|
1053
|
+
return `${nestedName}[]`;
|
|
950
1054
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (schema.allOf) {
|
|
956
|
-
const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
|
|
957
|
-
const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces);
|
|
958
|
-
return t.includes(" | ") ? `(${t})` : t;
|
|
959
|
-
});
|
|
960
|
-
return types.join(" & ");
|
|
961
|
-
}
|
|
962
|
-
if (type === "null") return "null";
|
|
963
|
-
if (type === "string") return "string";
|
|
964
|
-
if (type === "integer" || type === "number") return "number";
|
|
965
|
-
if (type === "boolean") return "boolean";
|
|
966
|
-
const arrayItems = schema["items"];
|
|
967
|
-
if (type === "array" && arrayItems) {
|
|
968
|
-
const items = arrayItems;
|
|
969
|
-
if (items.type === "object" || items.properties) {
|
|
970
|
-
const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
|
|
971
|
-
const nestedInterface = generateNestedInterface(nestedName, items);
|
|
972
|
-
nestedInterfaces.push(nestedInterface);
|
|
973
|
-
return `${nestedName}[]`;
|
|
1055
|
+
if (items.enum) {
|
|
1056
|
+
return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
|
|
1057
|
+
}
|
|
1058
|
+
return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
|
|
974
1059
|
}
|
|
975
|
-
if (
|
|
976
|
-
|
|
1060
|
+
if (type === "object" || schema.properties) {
|
|
1061
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}`;
|
|
1062
|
+
const nestedInterface = generateNestedInterface(nestedName, schema, seen);
|
|
1063
|
+
nestedInterfaces.push(nestedInterface);
|
|
1064
|
+
return nestedName;
|
|
977
1065
|
}
|
|
978
|
-
return
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
const nestedName = `${parentName}${toPascalCase(propertyName)}`;
|
|
982
|
-
const nestedInterface = generateNestedInterface(nestedName, schema);
|
|
983
|
-
nestedInterfaces.push(nestedInterface);
|
|
984
|
-
return nestedName;
|
|
1066
|
+
return "unknown";
|
|
1067
|
+
} finally {
|
|
1068
|
+
seen.delete(schema);
|
|
985
1069
|
}
|
|
986
|
-
return "unknown";
|
|
987
1070
|
}
|
|
988
|
-
function generateNestedInterface(name, schema) {
|
|
1071
|
+
function generateNestedInterface(name, schema, seen) {
|
|
989
1072
|
const lines = [];
|
|
990
1073
|
const nestedInterfaces = [];
|
|
991
1074
|
const required = new Set(schema.required ?? []);
|
|
@@ -993,7 +1076,7 @@ function generateNestedInterface(name, schema) {
|
|
|
993
1076
|
for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
|
|
994
1077
|
if (isReferenceObject(propSchema)) continue;
|
|
995
1078
|
const optional = required.has(propName) ? "" : "?";
|
|
996
|
-
const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
|
|
1079
|
+
const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
|
|
997
1080
|
lines.push(` ${propName}${optional}: ${tsType};`);
|
|
998
1081
|
}
|
|
999
1082
|
lines.push(`}`);
|
|
@@ -1465,6 +1548,12 @@ run().catch((error) => {
|
|
|
1465
1548
|
if (error.message.includes("Only OpenAPI 3.x")) {
|
|
1466
1549
|
logger.error("Only OpenAPI 3.x specifications are supported. Swagger 2.0 specs must be converted first.");
|
|
1467
1550
|
logger.info("Tip: Use https://converter.swagger.io to convert Swagger 2.0 to OpenAPI 3.0");
|
|
1551
|
+
} else if (error instanceof RangeError && /stack|recursion/i.test(error.message)) {
|
|
1552
|
+
logger.error("Schema generation failed: stack overflow during schema traversal.");
|
|
1553
|
+
logger.info("Tip: this usually means your spec contains a circular $ref chain (e.g. A \u2192 B \u2192 A).");
|
|
1554
|
+
logger.info(
|
|
1555
|
+
" Identify the cycle and either flatten it or annotate the back-edge with x-ng-forge-type to control how it renders."
|
|
1556
|
+
);
|
|
1468
1557
|
} else {
|
|
1469
1558
|
logger.error(error.message);
|
|
1470
1559
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-forge/openapi-generator",
|
|
3
|
-
"version": "0.9.0
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Generate @ng-forge/dynamic-forms configurations from OpenAPI specs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"sideEffects": false,
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@apidevtools/swagger-parser": "^10.1.1",
|
|
48
|
-
"commander": "^
|
|
48
|
+
"commander": "^14.0.3",
|
|
49
49
|
"@inquirer/prompts": "^8.3.0",
|
|
50
|
-
"chokidar": "^
|
|
50
|
+
"chokidar": "^5.0.0",
|
|
51
51
|
"chalk": "^5.4.1",
|
|
52
52
|
"openapi-types": "^12.1.3"
|
|
53
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface-generator.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/generator/interface-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,KAAK,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAEtE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"interface-generator.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/generator/interface-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,KAAK,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAEtE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAkClG"}
|
package/src/index.js
CHANGED
|
@@ -63,52 +63,60 @@ function formatEndpointLabel(endpoint) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// packages/openapi-generator/src/parser/schema-walker.ts
|
|
66
|
-
function walkSchema(schema, requiredFields = []) {
|
|
66
|
+
function walkSchema(schema, requiredFields = [], seen = /* @__PURE__ */ new WeakSet()) {
|
|
67
67
|
const warnings = [];
|
|
68
|
-
if (schema
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
if (schema.oneOf && schema.discriminator) {
|
|
72
|
-
return walkOneOfWithDiscriminator(
|
|
73
|
-
schema.oneOf,
|
|
74
|
-
schema.discriminator,
|
|
75
|
-
requiredFields,
|
|
76
|
-
warnings
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
if (schema.oneOf && !schema.discriminator) {
|
|
80
|
-
warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
|
|
81
|
-
return { properties: [], warnings };
|
|
82
|
-
}
|
|
83
|
-
if (schema.anyOf) {
|
|
84
|
-
warnings.push("anyOf schemas are not supported and were skipped");
|
|
85
|
-
return { properties: [], warnings };
|
|
68
|
+
if (seen.has(schema)) {
|
|
69
|
+
return { properties: [], warnings: ["Circular schema reference detected during allOf merge \u2014 nested schema skipped"] };
|
|
86
70
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
71
|
+
seen.add(schema);
|
|
72
|
+
try {
|
|
73
|
+
if (schema.allOf) {
|
|
74
|
+
return walkAllOf(schema.allOf, requiredFields, warnings, seen);
|
|
75
|
+
}
|
|
76
|
+
if (schema.oneOf && schema.discriminator) {
|
|
77
|
+
return walkOneOfWithDiscriminator(
|
|
78
|
+
schema.oneOf,
|
|
79
|
+
schema.discriminator,
|
|
80
|
+
requiredFields,
|
|
81
|
+
warnings
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (schema.oneOf && !schema.discriminator) {
|
|
85
|
+
warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
|
|
86
|
+
return { properties: [], warnings };
|
|
87
|
+
}
|
|
88
|
+
if (schema.anyOf) {
|
|
89
|
+
warnings.push("anyOf schemas are not supported and were skipped");
|
|
90
|
+
return { properties: [], warnings };
|
|
91
|
+
}
|
|
92
|
+
if ("if" in schema) {
|
|
93
|
+
warnings.push("if/then/else schemas are not supported and were skipped");
|
|
94
|
+
return { properties: [], warnings };
|
|
95
|
+
}
|
|
96
|
+
if (schema.additionalProperties && schema.additionalProperties !== false) {
|
|
97
|
+
warnings.push("additionalProperties are not supported and were skipped");
|
|
98
|
+
}
|
|
99
|
+
const properties = [];
|
|
100
|
+
const required = new Set(schema.required ?? requiredFields);
|
|
101
|
+
for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
|
|
102
|
+
if (isReferenceObject(propSchema)) continue;
|
|
103
|
+
properties.push({
|
|
104
|
+
name,
|
|
105
|
+
schema: propSchema,
|
|
106
|
+
required: required.has(name)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return { properties, warnings };
|
|
110
|
+
} finally {
|
|
111
|
+
seen.delete(schema);
|
|
103
112
|
}
|
|
104
|
-
return { properties, warnings };
|
|
105
113
|
}
|
|
106
|
-
function walkAllOf(schemas, requiredFields, warnings) {
|
|
114
|
+
function walkAllOf(schemas, requiredFields, warnings, seen) {
|
|
107
115
|
const mergedProperties = /* @__PURE__ */ new Map();
|
|
108
116
|
const allRequired = new Set(requiredFields);
|
|
109
117
|
for (const schema of schemas) {
|
|
110
118
|
if (isReferenceObject(schema)) continue;
|
|
111
|
-
const walked = walkSchema(schema, []);
|
|
119
|
+
const walked = walkSchema(schema, [], seen);
|
|
112
120
|
warnings.push(...walked.warnings);
|
|
113
121
|
for (const prop of walked.properties) {
|
|
114
122
|
mergedProperties.set(prop.name, prop);
|
|
@@ -445,7 +453,17 @@ var logger = {
|
|
|
445
453
|
};
|
|
446
454
|
|
|
447
455
|
// packages/openapi-generator/src/mapper/schema-to-fields.ts
|
|
448
|
-
var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
456
|
+
var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
457
|
+
"input",
|
|
458
|
+
"textarea",
|
|
459
|
+
"select",
|
|
460
|
+
"radio",
|
|
461
|
+
"multi-checkbox",
|
|
462
|
+
"slider",
|
|
463
|
+
"datepicker",
|
|
464
|
+
"checkbox",
|
|
465
|
+
"toggle"
|
|
466
|
+
]);
|
|
449
467
|
var CONTAINER_FIELD_TYPES = /* @__PURE__ */ new Set(["group", "array", "row", "page", "container"]);
|
|
450
468
|
function singularize(label) {
|
|
451
469
|
if (label.length >= 4 && label.endsWith("s") && !label.endsWith("ss")) {
|
|
@@ -454,54 +472,76 @@ function singularize(label) {
|
|
|
454
472
|
return label;
|
|
455
473
|
}
|
|
456
474
|
function mapSchemaToFields(schema, requiredFields, options = {}) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
475
|
+
return mapSchemaToFieldsInternal(schema, requiredFields, options, /* @__PURE__ */ new WeakSet());
|
|
476
|
+
}
|
|
477
|
+
function mapSchemaToFieldsInternal(schema, requiredFields, options, seen) {
|
|
478
|
+
if (seen.has(schema)) {
|
|
479
|
+
return {
|
|
480
|
+
fields: [],
|
|
481
|
+
ambiguousFields: [],
|
|
482
|
+
warnings: [
|
|
483
|
+
`Circular schema reference at '${options.schemaName ?? "<root>"}' \u2014 nested fields skipped. Use x-ng-forge-type to override how this field renders.`
|
|
484
|
+
]
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
seen.add(schema);
|
|
488
|
+
try {
|
|
489
|
+
const walked = walkSchema(schema, requiredFields);
|
|
490
|
+
const ambiguousFields = [];
|
|
491
|
+
const warnings = [...walked.warnings];
|
|
492
|
+
if (walked.discriminator) {
|
|
493
|
+
const discConfig = mapDiscriminator(walked.discriminator);
|
|
494
|
+
const fields2 = [discConfig.discriminatorField];
|
|
495
|
+
for (const group of discConfig.conditionalGroups) {
|
|
496
|
+
const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
|
|
497
|
+
if (variantSchema) {
|
|
498
|
+
const variantResult = mapSchemaToFieldsInternal(
|
|
499
|
+
variantSchema,
|
|
500
|
+
variantSchema.required ?? [],
|
|
501
|
+
{
|
|
502
|
+
...options,
|
|
503
|
+
schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
|
|
504
|
+
},
|
|
505
|
+
seen
|
|
506
|
+
);
|
|
507
|
+
const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
|
|
508
|
+
ambiguousFields.push(...variantResult.ambiguousFields);
|
|
509
|
+
warnings.push(...variantResult.warnings);
|
|
510
|
+
if (variantFields.length > 0) {
|
|
511
|
+
fields2.push({
|
|
512
|
+
key: `${group.discriminatorValue}Variant`,
|
|
513
|
+
type: "group",
|
|
514
|
+
fields: variantFields,
|
|
515
|
+
logic: [
|
|
516
|
+
{
|
|
517
|
+
type: "hidden",
|
|
518
|
+
condition: {
|
|
519
|
+
type: "fieldValue",
|
|
520
|
+
fieldPath: walked.discriminator.propertyName,
|
|
521
|
+
operator: "notEquals",
|
|
522
|
+
value: group.discriminatorValue
|
|
523
|
+
}
|
|
486
524
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
525
|
+
]
|
|
526
|
+
});
|
|
527
|
+
}
|
|
490
528
|
}
|
|
491
529
|
}
|
|
530
|
+
return { fields: fields2, ambiguousFields, warnings };
|
|
492
531
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
fields.push(field);
|
|
532
|
+
const fields = [];
|
|
533
|
+
for (const prop of walked.properties) {
|
|
534
|
+
const field = mapPropertyToField(prop, options, seen, ambiguousFields, warnings);
|
|
535
|
+
if (field) {
|
|
536
|
+
fields.push(field);
|
|
537
|
+
}
|
|
500
538
|
}
|
|
539
|
+
return { fields, ambiguousFields, warnings };
|
|
540
|
+
} finally {
|
|
541
|
+
seen.delete(schema);
|
|
501
542
|
}
|
|
502
|
-
return { fields, ambiguousFields, warnings };
|
|
503
543
|
}
|
|
504
|
-
function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
544
|
+
function mapPropertyToField(prop, options, seen, ambiguousFields, warnings) {
|
|
505
545
|
if (prop.schema["deprecated"]) {
|
|
506
546
|
warnings.push(`Property '${prop.name}' is deprecated and was skipped`);
|
|
507
547
|
return void 0;
|
|
@@ -509,10 +549,12 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
509
549
|
if (prop.schema.oneOf && prop.schema["discriminator"]) {
|
|
510
550
|
const schemaPrefix2 = options.schemaName ?? "";
|
|
511
551
|
const nestedSchemaName = schemaPrefix2 ? `${schemaPrefix2}.${prop.name}` : prop.name;
|
|
512
|
-
const innerResult =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
552
|
+
const innerResult = mapSchemaToFieldsInternal(
|
|
553
|
+
prop.schema,
|
|
554
|
+
prop.schema.required ?? [],
|
|
555
|
+
{ ...options, schemaName: nestedSchemaName },
|
|
556
|
+
seen
|
|
557
|
+
);
|
|
516
558
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
517
559
|
warnings.push(...innerResult.warnings);
|
|
518
560
|
return {
|
|
@@ -542,19 +584,19 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
542
584
|
}
|
|
543
585
|
}
|
|
544
586
|
const validators = mapSchemaToValidators(prop.schema, prop.required);
|
|
587
|
+
const field = {
|
|
588
|
+
key: prop.name,
|
|
589
|
+
type: finalType
|
|
590
|
+
};
|
|
545
591
|
const arraySchema = prop.schema;
|
|
546
|
-
if (
|
|
592
|
+
if (finalType === "array") {
|
|
547
593
|
if (arraySchema["minItems"] !== void 0) {
|
|
548
|
-
|
|
594
|
+
field.minLength = arraySchema["minItems"];
|
|
549
595
|
}
|
|
550
596
|
if (arraySchema["maxItems"] !== void 0) {
|
|
551
|
-
|
|
597
|
+
field.maxLength = arraySchema["maxItems"];
|
|
552
598
|
}
|
|
553
599
|
}
|
|
554
|
-
const field = {
|
|
555
|
-
key: prop.name,
|
|
556
|
-
type: finalType
|
|
557
|
-
};
|
|
558
600
|
if (!CONTAINER_FIELD_TYPES.has(finalType)) {
|
|
559
601
|
field.label = prop.schema["title"] ?? toLabel(prop.name);
|
|
560
602
|
}
|
|
@@ -598,7 +640,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
598
640
|
}
|
|
599
641
|
}
|
|
600
642
|
if (validators.length > 0) {
|
|
601
|
-
|
|
643
|
+
if (CONTAINER_FIELD_TYPES.has(finalType)) {
|
|
644
|
+
const droppedTypes = validators.map((v) => v.type).join(", ");
|
|
645
|
+
const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
|
|
646
|
+
logger.verbose(
|
|
647
|
+
`Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
|
|
648
|
+
);
|
|
649
|
+
} else {
|
|
650
|
+
field.validators = validators;
|
|
651
|
+
}
|
|
602
652
|
}
|
|
603
653
|
if (options.editable === false) {
|
|
604
654
|
field.disabled = true;
|
|
@@ -625,14 +675,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
|
|
|
625
675
|
const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
|
|
626
676
|
const nestedOptions = { ...options, schemaName: nestedSchemaName };
|
|
627
677
|
if (typeResult.fieldType === "group" && prop.schema.properties) {
|
|
628
|
-
const innerResult =
|
|
678
|
+
const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
|
|
629
679
|
field.fields = innerResult.fields;
|
|
630
680
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
631
681
|
} else if (typeResult.fieldType === "array" && prop.schema["items"]) {
|
|
632
682
|
const items = prop.schema["items"];
|
|
633
683
|
const fieldLabel = singularize(toLabel(prop.name));
|
|
634
684
|
if (items.type === "object" || items.properties) {
|
|
635
|
-
const innerResult =
|
|
685
|
+
const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
|
|
636
686
|
field.template = innerResult.fields;
|
|
637
687
|
ambiguousFields.push(...innerResult.ambiguousFields);
|
|
638
688
|
warnings.push(...innerResult.warnings);
|
|
@@ -732,6 +782,12 @@ function generateFieldLines(field, indent) {
|
|
|
732
782
|
}
|
|
733
783
|
lines.push(`${indent} ],`);
|
|
734
784
|
}
|
|
785
|
+
if (field.minLength !== void 0) {
|
|
786
|
+
lines.push(`${indent} minLength: ${field.minLength},`);
|
|
787
|
+
}
|
|
788
|
+
if (field.maxLength !== void 0) {
|
|
789
|
+
lines.push(`${indent} maxLength: ${field.maxLength},`);
|
|
790
|
+
}
|
|
735
791
|
if (field.disabled) {
|
|
736
792
|
lines.push(`${indent} disabled: true,`);
|
|
737
793
|
}
|
|
@@ -820,15 +876,21 @@ function generateInterface(schema, options) {
|
|
|
820
876
|
const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
|
|
821
877
|
const lines = [];
|
|
822
878
|
const nestedInterfaces = [];
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
879
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
880
|
+
seen.add(schema);
|
|
881
|
+
try {
|
|
882
|
+
const { properties, required } = resolveSchemaProperties(schema, seen);
|
|
883
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
884
|
+
for (const [name, propSchema] of properties) {
|
|
885
|
+
if (isReferenceObject(propSchema)) continue;
|
|
886
|
+
const optional = required.has(name) ? "" : "?";
|
|
887
|
+
const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
|
|
888
|
+
lines.push(` ${name}${optional}: ${tsType};`);
|
|
889
|
+
}
|
|
890
|
+
lines.push(`}`);
|
|
891
|
+
} finally {
|
|
892
|
+
seen.delete(schema);
|
|
830
893
|
}
|
|
831
|
-
lines.push(`}`);
|
|
832
894
|
if (nestedInterfaces.length > 0) {
|
|
833
895
|
return `// @generated by @ng-forge/openapi-generator
|
|
834
896
|
` + [...nestedInterfaces, "", ...lines, ""].join("\n");
|
|
@@ -837,18 +899,24 @@ function generateInterface(schema, options) {
|
|
|
837
899
|
return `// @generated by @ng-forge/openapi-generator
|
|
838
900
|
` + lines.join("\n");
|
|
839
901
|
}
|
|
840
|
-
function resolveSchemaProperties(schema) {
|
|
902
|
+
function resolveSchemaProperties(schema, seen) {
|
|
841
903
|
if (schema.allOf) {
|
|
842
904
|
const merged = /* @__PURE__ */ new Map();
|
|
843
905
|
const allRequired = new Set(schema.required ?? []);
|
|
844
906
|
for (const sub of schema.allOf) {
|
|
845
907
|
if (isReferenceObject(sub)) continue;
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
908
|
+
if (seen.has(sub)) continue;
|
|
909
|
+
seen.add(sub);
|
|
910
|
+
try {
|
|
911
|
+
const resolved = resolveSchemaProperties(sub, seen);
|
|
912
|
+
for (const [name, propSchema] of resolved.properties) {
|
|
913
|
+
merged.set(name, propSchema);
|
|
914
|
+
}
|
|
915
|
+
for (const r of resolved.required) {
|
|
916
|
+
allRequired.add(r);
|
|
917
|
+
}
|
|
918
|
+
} finally {
|
|
919
|
+
seen.delete(sub);
|
|
852
920
|
}
|
|
853
921
|
}
|
|
854
922
|
return { properties: Array.from(merged.entries()), required: allRequired };
|
|
@@ -860,7 +928,14 @@ function resolveSchemaProperties(schema) {
|
|
|
860
928
|
const discriminatorValues = [];
|
|
861
929
|
for (const variant of schema.oneOf) {
|
|
862
930
|
if (isReferenceObject(variant)) continue;
|
|
863
|
-
|
|
931
|
+
if (seen.has(variant)) continue;
|
|
932
|
+
seen.add(variant);
|
|
933
|
+
let resolved;
|
|
934
|
+
try {
|
|
935
|
+
resolved = resolveSchemaProperties(variant, seen);
|
|
936
|
+
} finally {
|
|
937
|
+
seen.delete(variant);
|
|
938
|
+
}
|
|
864
939
|
for (const [name, propSchema] of resolved.properties) {
|
|
865
940
|
if (name === discriminatorName) {
|
|
866
941
|
if (propSchema.enum) {
|
|
@@ -906,78 +981,86 @@ function resolveSchemaProperties(schema) {
|
|
|
906
981
|
}
|
|
907
982
|
return { properties, required };
|
|
908
983
|
}
|
|
909
|
-
function schemaToTsType(propertyName, schema, parentName, nestedInterfaces) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
type
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
984
|
+
function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
|
|
985
|
+
if (seen.has(schema)) {
|
|
986
|
+
return "unknown";
|
|
987
|
+
}
|
|
988
|
+
seen.add(schema);
|
|
989
|
+
try {
|
|
990
|
+
const rawType = schema["type"];
|
|
991
|
+
let type;
|
|
992
|
+
let nullableFrom31Type = false;
|
|
993
|
+
if (Array.isArray(rawType)) {
|
|
994
|
+
const nonNull = rawType.filter((t) => t !== "null");
|
|
995
|
+
nullableFrom31Type = nonNull.length !== rawType.length;
|
|
996
|
+
type = nonNull[0];
|
|
997
|
+
} else {
|
|
998
|
+
type = rawType;
|
|
999
|
+
}
|
|
1000
|
+
const isNullable = schema["nullable"] === true || nullableFrom31Type;
|
|
1001
|
+
const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
|
|
1002
|
+
if (enumValues && enumValues.length > 0) {
|
|
1003
|
+
const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
1004
|
+
return isNullable ? `${enumUnion} | null` : enumUnion;
|
|
1005
|
+
}
|
|
1006
|
+
if (isNullable) {
|
|
1007
|
+
const baseSchema = { ...schema, nullable: void 0, type };
|
|
1008
|
+
const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
|
|
1009
|
+
return `${baseType} | null`;
|
|
1010
|
+
}
|
|
1011
|
+
const unionSchemas = schema.oneOf ?? schema.anyOf;
|
|
1012
|
+
if (unionSchemas) {
|
|
1013
|
+
const disc = schema["discriminator"];
|
|
1014
|
+
const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
|
|
1015
|
+
const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
|
|
1016
|
+
if ((s.type === "object" || s.properties) && mappingKeys) {
|
|
1017
|
+
const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
|
|
1018
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
|
|
1019
|
+
const nestedInterface = generateNestedInterface(nestedName, s, seen);
|
|
1020
|
+
nestedInterfaces.push(nestedInterface);
|
|
1021
|
+
return nestedName;
|
|
1022
|
+
}
|
|
1023
|
+
return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
|
|
1024
|
+
});
|
|
1025
|
+
return types.join(" | ");
|
|
1026
|
+
}
|
|
1027
|
+
if (schema.allOf) {
|
|
1028
|
+
const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
|
|
1029
|
+
const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
|
|
1030
|
+
return t.includes(" | ") ? `(${t})` : t;
|
|
1031
|
+
});
|
|
1032
|
+
return types.join(" & ");
|
|
1033
|
+
}
|
|
1034
|
+
if (type === "null") return "null";
|
|
1035
|
+
if (type === "string") return "string";
|
|
1036
|
+
if (type === "integer" || type === "number") return "number";
|
|
1037
|
+
if (type === "boolean") return "boolean";
|
|
1038
|
+
const arrayItems = schema["items"];
|
|
1039
|
+
if (type === "array" && arrayItems) {
|
|
1040
|
+
const items = arrayItems;
|
|
1041
|
+
if (items.type === "object" || items.properties) {
|
|
1042
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
|
|
1043
|
+
const nestedInterface = generateNestedInterface(nestedName, items, seen);
|
|
940
1044
|
nestedInterfaces.push(nestedInterface);
|
|
941
|
-
return nestedName
|
|
1045
|
+
return `${nestedName}[]`;
|
|
942
1046
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
if (schema.allOf) {
|
|
948
|
-
const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
|
|
949
|
-
const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces);
|
|
950
|
-
return t.includes(" | ") ? `(${t})` : t;
|
|
951
|
-
});
|
|
952
|
-
return types.join(" & ");
|
|
953
|
-
}
|
|
954
|
-
if (type === "null") return "null";
|
|
955
|
-
if (type === "string") return "string";
|
|
956
|
-
if (type === "integer" || type === "number") return "number";
|
|
957
|
-
if (type === "boolean") return "boolean";
|
|
958
|
-
const arrayItems = schema["items"];
|
|
959
|
-
if (type === "array" && arrayItems) {
|
|
960
|
-
const items = arrayItems;
|
|
961
|
-
if (items.type === "object" || items.properties) {
|
|
962
|
-
const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
|
|
963
|
-
const nestedInterface = generateNestedInterface(nestedName, items);
|
|
964
|
-
nestedInterfaces.push(nestedInterface);
|
|
965
|
-
return `${nestedName}[]`;
|
|
1047
|
+
if (items.enum) {
|
|
1048
|
+
return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
|
|
1049
|
+
}
|
|
1050
|
+
return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
|
|
966
1051
|
}
|
|
967
|
-
if (
|
|
968
|
-
|
|
1052
|
+
if (type === "object" || schema.properties) {
|
|
1053
|
+
const nestedName = `${parentName}${toPascalCase(propertyName)}`;
|
|
1054
|
+
const nestedInterface = generateNestedInterface(nestedName, schema, seen);
|
|
1055
|
+
nestedInterfaces.push(nestedInterface);
|
|
1056
|
+
return nestedName;
|
|
969
1057
|
}
|
|
970
|
-
return
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
const nestedName = `${parentName}${toPascalCase(propertyName)}`;
|
|
974
|
-
const nestedInterface = generateNestedInterface(nestedName, schema);
|
|
975
|
-
nestedInterfaces.push(nestedInterface);
|
|
976
|
-
return nestedName;
|
|
1058
|
+
return "unknown";
|
|
1059
|
+
} finally {
|
|
1060
|
+
seen.delete(schema);
|
|
977
1061
|
}
|
|
978
|
-
return "unknown";
|
|
979
1062
|
}
|
|
980
|
-
function generateNestedInterface(name, schema) {
|
|
1063
|
+
function generateNestedInterface(name, schema, seen) {
|
|
981
1064
|
const lines = [];
|
|
982
1065
|
const nestedInterfaces = [];
|
|
983
1066
|
const required = new Set(schema.required ?? []);
|
|
@@ -985,7 +1068,7 @@ function generateNestedInterface(name, schema) {
|
|
|
985
1068
|
for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
|
|
986
1069
|
if (isReferenceObject(propSchema)) continue;
|
|
987
1070
|
const optional = required.has(propName) ? "" : "?";
|
|
988
|
-
const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
|
|
1071
|
+
const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
|
|
989
1072
|
lines.push(` ${propName}${optional}: ${tsType};`);
|
|
990
1073
|
}
|
|
991
1074
|
lines.push(`}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-to-fields.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/mapper/schema-to-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,4BAA4B,CAAC;AAG/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"schema-to-fields.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/mapper/schema-to-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,4BAA4B,CAAC;AAG/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4C9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACvC,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IACvE,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IAC1E,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IAItB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAI7H"}
|
|
@@ -13,5 +13,5 @@ export interface WalkedSchema {
|
|
|
13
13
|
};
|
|
14
14
|
warnings: string[];
|
|
15
15
|
}
|
|
16
|
-
export declare function walkSchema(schema: SchemaObject, requiredFields?: string[]): WalkedSchema;
|
|
16
|
+
export declare function walkSchema(schema: SchemaObject, requiredFields?: string[], seen?: WeakSet<SchemaObject>): WalkedSchema;
|
|
17
17
|
//# sourceMappingURL=schema-walker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-walker.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/parser/schema-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5D,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,GAAE,MAAM,EAAO,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"schema-walker.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/parser/schema-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5D,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,GAAE,MAAM,EAAO,EAAE,IAAI,GAAE,OAAO,CAAC,YAAY,CAAiB,GAAG,YAAY,CAoEzI"}
|