@@ -12,12 +12,13 @@ import {
12
12
resolvePath,
13
13
} from '@zenstackhq/sdk';
14
14
import { DataModel, DataModelField, DataModelFieldType, Enum, isDataModel, isEnum } from '@zenstackhq/sdk/ast';
15
- import * as fs from 'fs';
15
+ import fs from 'fs';
16
16
import { lowerCaseFirst } from 'lower-case-first';
17
17
import type { OpenAPIV3_1 as OAPI } from 'openapi-types';
18
- import * as path from 'path';
18
+ import path from 'path';
19
19
import pluralize from 'pluralize';
20
20
import invariant from 'tiny-invariant';
21
+ import { P, match } from 'ts-pattern';
21
22
import YAML from 'yaml';
22
23
import { name } from '.';
23
24
import { OpenAPIGeneratorBase } from './generator-base';
@@ -49,7 +50,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
49
50
}
50
51
51
52
const openapi: OAPI.Document = {
52
- openapi: this.getOption('specVersion', '3.1.0' ),
53
+ openapi: this.getOption('specVersion', this.DEFAULT_SPEC_VERSION ),
53
54
info: {
54
55
title: this.getOption('title', 'ZenStack Generated API'),
55
56
version: this.getOption('version', '1.0.0'),
@@ -483,9 +484,8 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
483
484
schema = this.fieldTypeToOpenAPISchema(field.type);
484
485
}
485
486
}
486
- if (array) {
487
- schema = { type: 'array', items: schema };
488
- }
487
+
488
+ schema = this.wrapArray(schema, array);
489
489
490
490
return {
491
491
name: name === 'id' ? 'filter[id]' : `filter[${field.name}${name}]`,
@@ -576,10 +576,10 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
576
576
description: 'Pagination information',
577
577
required: ['first', 'last', 'prev', 'next'],
578
578
properties: {
579
- first: this.nullable ({ type: 'string', description: 'Link to the first page' }),
580
- last: this.nullable ({ type: 'string', description: 'Link to the last page' }),
581
- prev: this.nullable ({ type: 'string', description: 'Link to the previous page' }),
582
- next: this.nullable ({ type: 'string', description: 'Link to the next page' }),
579
+ first: this.wrapNullable ({ type: 'string', description: 'Link to the first page' }, true ),
580
+ last: this.wrapNullable ({ type: 'string', description: 'Link to the last page' }, true ),
581
+ prev: this.wrapNullable ({ type: 'string', description: 'Link to the previous page' }, true ),
582
+ next: this.wrapNullable ({ type: 'string', description: 'Link to the next page' }, true ),
583
583
},
584
584
},
585
585
_errors: {
@@ -634,7 +634,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
634
634
type: 'object',
635
635
description: 'A to-one relationship',
636
636
properties: {
637
- data: this.nullable (this.ref('_resourceIdentifier')),
637
+ data: this.wrapNullable (this.ref('_resourceIdentifier'), true ),
638
638
},
639
639
},
640
640
_toOneRelationshipWithLinks: {
@@ -643,7 +643,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
643
643
description: 'A to-one relationship with links',
644
644
properties: {
645
645
links: this.ref('_relationLinks'),
646
- data: this.nullable (this.ref('_resourceIdentifier')),
646
+ data: this.wrapNullable (this.ref('_resourceIdentifier'), true ),
647
647
},
648
648
},
649
649
_toManyRelationship: {
@@ -680,13 +680,16 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
680
680
},
681
681
_toOneRelationshipRequest: {
682
682
description: 'Input for manipulating a to-one relationship',
683
- ...this.nullable({
684
- type: 'object',
685
- required: ['data'],
686
- properties: {
687
- data: this.ref('_resourceIdentifier'),
683
+ ...this.wrapNullable(
684
+ {
685
+ type: 'object',
686
+ required: ['data'],
687
+ properties: {
688
+ data: this.ref('_resourceIdentifier'),
689
+ },
688
690
},
689
- }),
691
+ true
692
+ ),
690
693
},
691
694
_toManyRelationshipResponse: {
692
695
description: 'Response for a to-many relationship',
@@ -841,7 +844,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
841
844
const fields = model.fields.filter((f) => !isIdField(f));
842
845
843
846
const attributes: Record<string, OAPI.SchemaObject> = {};
844
- const relationships: Record<string, OAPI.ReferenceObject> = {};
847
+ const relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject > = {};
845
848
846
849
const required: string[] = [];
847
850
@@ -853,7 +856,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
853
856
} else {
854
857
relType = field.type.array ? '_toManyRelationshipWithLinks' : '_toOneRelationshipWithLinks';
855
858
}
856
- relationships[field.name] = this.ref(relType);
859
+ relationships[field.name] = this.wrapNullable(this. ref(relType), field.type.optional );
857
860
} else {
858
861
attributes[field.name] = this.generateField(field);
859
862
if (
@@ -911,48 +914,33 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
911
914
}
912
915
913
916
private generateField(field: DataModelField) {
914
- return this.wrapArray(this.fieldTypeToOpenAPISchema(field.type), field.type.array);
915
- }
916
-
917
- private get specVersion() {
918
- return this.getOption('specVersion', '3.0.0');
917
+ return this.wrapArray(
918
+ this.wrapNullable(this.fieldTypeToOpenAPISchema(field.type), field.type.optional),
919
+ field.type.array
920
+ );
919
921
}
920
922
921
923
private fieldTypeToOpenAPISchema(type: DataModelFieldType): OAPI.ReferenceObject | OAPI.SchemaObject {
922
- switch (type.type) {
923
- case 'String':
924
- return { type: 'string' };
925
- case 'Int':
926
- case 'BigInt':
927
- return { type: 'integer' };
928
- case 'Float':
929
- return { type: 'number' };
930
- case 'Decimal':
931
- return this.oneOf({ type: 'number' }, { type: 'string' });
932
- case 'Boolean':
933
- return { type: 'boolean' };
934
- case 'DateTime':
935
- return { type: 'string', format: 'date-time' };
936
- case 'Bytes':
937
- return { type: 'string', format: 'byte', description: 'Base64 encoded byte array' };
938
- case 'Json':
939
- return {};
940
- default: {
924
+ return match(type.type)
925
+ .with('String', () => ({ type: 'string' }))
926
+ .with(P.union('Int', 'BigInt'), () => ({ type: 'integer' }))
927
+ .with('Float', () => ({ type: 'number' }))
928
+ .with('Decimal', () => this.oneOf({ type: 'number' }, { type: 'string' }))
929
+ .with('Boolean', () => ({ type: 'boolean' }))
930
+ .with('DateTime', () => ({ type: 'string', format: 'date-time' }))
931
+ .with('Bytes', () => ({ type: 'string', format: 'byte', description: 'Base64 encoded byte array' }))
932
+ .with('Json', () => ({}))
933
+ .otherwise((t) => {
941
934
const fieldDecl = type.reference?.ref;
942
- invariant(fieldDecl);
935
+ invariant(fieldDecl, `Type ${t} is not a model reference` );
943
936
return this.ref(fieldDecl?.name);
944
- }
945
- }
937
+ });
946
938
}
947
939
948
940
private ref(type: string) {
949
941
return { $ref: `#/components/schemas/${type}` };
950
942
}
951
943
952
- private nullable(schema: OAPI.SchemaObject | OAPI.ReferenceObject) {
953
- return this.specVersion === '3.0.0' ? { ...schema, nullable: true } : this.oneOf(schema, { type: 'null' });
954
- }
955
-
956
944
private parameter(type: string) {
957
945
return { $ref: `#/components/parameters/${type}` };
958
946
}
0 commit comments