|
1 | 1 | import { lowerCaseFirst } from '@zenstackhq/common-helpers'; |
2 | | -import type { EnumDef, FieldDef, ModelDef, SchemaDef } from '@zenstackhq/orm/schema'; |
| 2 | +import type { EnumDef, FieldDef, ModelDef, SchemaDef, TypeDefDef } from '@zenstackhq/orm/schema'; |
3 | 3 | import type { OpenAPIV3_1 } from 'openapi-types'; |
4 | 4 | import { PROCEDURE_ROUTE_PREFIXES } from '../common/procedures'; |
5 | 5 | import { |
@@ -524,6 +524,13 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> { |
524 | 524 | } |
525 | 525 | } |
526 | 526 |
|
| 527 | + // Per-typeDef schemas |
| 528 | + if (this.schema.typeDefs) { |
| 529 | + for (const [typeName, typeDef] of Object.entries(this.schema.typeDefs)) { |
| 530 | + schemas[typeName] = this.buildTypeDefSchema(typeDef); |
| 531 | + } |
| 532 | + } |
| 533 | + |
527 | 534 | // Per-model schemas |
528 | 535 | for (const modelName of getIncludedModels(this.schema, this.queryOptions)) { |
529 | 536 | const modelDef = this.schema.models[modelName]!; |
@@ -710,31 +717,66 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> { |
710 | 717 | }; |
711 | 718 | } |
712 | 719 |
|
713 | | - private buildModelReadSchema(modelName: string, modelDef: ModelDef): SchemaObject { |
| 720 | + private buildTypeDefSchema(typeDef: TypeDefDef): SchemaObject { |
714 | 721 | const properties: Record<string, SchemaObject | ReferenceObject> = {}; |
715 | 722 | const required: string[] = []; |
716 | 723 |
|
| 724 | + for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { |
| 725 | + properties[fieldName] = this.fieldToSchema(fieldDef); |
| 726 | + if (!fieldDef.optional && !fieldDef.array) { |
| 727 | + required.push(fieldName); |
| 728 | + } |
| 729 | + } |
| 730 | + |
| 731 | + const result: SchemaObject = { type: 'object', properties }; |
| 732 | + if (required.length > 0) { |
| 733 | + result.required = required; |
| 734 | + } |
| 735 | + return result; |
| 736 | + } |
| 737 | + |
| 738 | + private buildModelReadSchema(modelName: string, modelDef: ModelDef): SchemaObject { |
| 739 | + const attrProperties: Record<string, SchemaObject | ReferenceObject> = {}; |
| 740 | + const attrRequired: string[] = []; |
| 741 | + const relProperties: Record<string, SchemaObject | ReferenceObject> = {}; |
| 742 | + |
717 | 743 | for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { |
718 | 744 | if (fieldDef.omit) continue; |
719 | 745 | if (isFieldOmitted(modelName, fieldName, this.queryOptions)) continue; |
720 | 746 | if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue; |
721 | 747 |
|
722 | | - const schema = this.fieldToSchema(fieldDef); |
723 | | - const fieldDescription = getMetaDescription(fieldDef.attributes); |
724 | | - if (fieldDescription && !('$ref' in schema)) { |
725 | | - schema.description = fieldDescription; |
726 | | - } |
727 | | - properties[fieldName] = schema; |
| 748 | + if (fieldDef.relation) { |
| 749 | + const relRef: SchemaObject | ReferenceObject = fieldDef.array |
| 750 | + ? { $ref: '#/components/schemas/_toManyRelationshipWithLinks' } |
| 751 | + : { $ref: '#/components/schemas/_toOneRelationshipWithLinks' }; |
| 752 | + relProperties[fieldName] = fieldDef.optional ? { oneOf: [{ type: 'null' }, relRef] } : relRef; |
| 753 | + } else { |
| 754 | + const schema = this.fieldToSchema(fieldDef); |
| 755 | + const fieldDescription = getMetaDescription(fieldDef.attributes); |
| 756 | + if (fieldDescription && !('$ref' in schema)) { |
| 757 | + schema.description = fieldDescription; |
| 758 | + } |
| 759 | + attrProperties[fieldName] = schema; |
728 | 760 |
|
729 | | - if (!fieldDef.optional && !fieldDef.array) { |
730 | | - required.push(fieldName); |
| 761 | + if (!fieldDef.optional && !fieldDef.array) { |
| 762 | + attrRequired.push(fieldName); |
| 763 | + } |
731 | 764 | } |
732 | 765 | } |
733 | 766 |
|
734 | | - const result: SchemaObject = { type: 'object', properties }; |
735 | | - if (required.length > 0) { |
736 | | - result.required = required; |
| 767 | + const properties: Record<string, SchemaObject | ReferenceObject> = {}; |
| 768 | + |
| 769 | + if (Object.keys(attrProperties).length > 0) { |
| 770 | + const attrSchema: SchemaObject = { type: 'object', properties: attrProperties }; |
| 771 | + if (attrRequired.length > 0) attrSchema.required = attrRequired; |
| 772 | + properties['attributes'] = attrSchema; |
737 | 773 | } |
| 774 | + |
| 775 | + if (Object.keys(relProperties).length > 0) { |
| 776 | + properties['relationships'] = { type: 'object', properties: relProperties }; |
| 777 | + } |
| 778 | + |
| 779 | + const result: SchemaObject = { type: 'object', properties }; |
738 | 780 | const description = getMetaDescription(modelDef.attributes); |
739 | 781 | if (description) { |
740 | 782 | result.description = description; |
|
0 commit comments