@@ -12,12 +12,13 @@ import {
1212 resolvePath ,
1313} from '@zenstackhq/sdk' ;
1414import { DataModel , DataModelField , DataModelFieldType , Enum , isDataModel , isEnum } from '@zenstackhq/sdk/ast' ;
15- import * as fs from 'fs' ;
15+ import fs from 'fs' ;
1616import { lowerCaseFirst } from 'lower-case-first' ;
1717import type { OpenAPIV3_1 as OAPI } from 'openapi-types' ;
18- import * as path from 'path' ;
18+ import path from 'path' ;
1919import pluralize from 'pluralize' ;
2020import invariant from 'tiny-invariant' ;
21+ import { P , match } from 'ts-pattern' ;
2122import YAML from 'yaml' ;
2223import { name } from '.' ;
2324import { OpenAPIGeneratorBase } from './generator-base' ;
@@ -49,7 +50,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
4950 }
5051
5152 const openapi : OAPI . Document = {
52- openapi : this . getOption ( 'specVersion' , '3.1.0' ) ,
53+ openapi : this . getOption ( 'specVersion' , this . DEFAULT_SPEC_VERSION ) ,
5354 info : {
5455 title : this . getOption ( 'title' , 'ZenStack Generated API' ) ,
5556 version : this . getOption ( 'version' , '1.0.0' ) ,
@@ -483,9 +484,8 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
483484 schema = this . fieldTypeToOpenAPISchema ( field . type ) ;
484485 }
485486 }
486- if ( array ) {
487- schema = { type : 'array' , items : schema } ;
488- }
487+
488+ schema = this . wrapArray ( schema , array ) ;
489489
490490 return {
491491 name : name === 'id' ? 'filter[id]' : `filter[${ field . name } ${ name } ]` ,
@@ -576,10 +576,10 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
576576 description : 'Pagination information' ,
577577 required : [ 'first' , 'last' , 'prev' , 'next' ] ,
578578 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 ) ,
583583 } ,
584584 } ,
585585 _errors : {
@@ -634,7 +634,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
634634 type : 'object' ,
635635 description : 'A to-one relationship' ,
636636 properties : {
637- data : this . nullable ( this . ref ( '_resourceIdentifier' ) ) ,
637+ data : this . wrapNullable ( this . ref ( '_resourceIdentifier' ) , true ) ,
638638 } ,
639639 } ,
640640 _toOneRelationshipWithLinks : {
@@ -643,7 +643,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
643643 description : 'A to-one relationship with links' ,
644644 properties : {
645645 links : this . ref ( '_relationLinks' ) ,
646- data : this . nullable ( this . ref ( '_resourceIdentifier' ) ) ,
646+ data : this . wrapNullable ( this . ref ( '_resourceIdentifier' ) , true ) ,
647647 } ,
648648 } ,
649649 _toManyRelationship : {
@@ -680,13 +680,16 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
680680 } ,
681681 _toOneRelationshipRequest : {
682682 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+ } ,
688690 } ,
689- } ) ,
691+ true
692+ ) ,
690693 } ,
691694 _toManyRelationshipResponse : {
692695 description : 'Response for a to-many relationship' ,
@@ -841,7 +844,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
841844 const fields = model . fields . filter ( ( f ) => ! isIdField ( f ) ) ;
842845
843846 const attributes : Record < string , OAPI . SchemaObject > = { } ;
844- const relationships : Record < string , OAPI . ReferenceObject > = { } ;
847+ const relationships : Record < string , OAPI . ReferenceObject | OAPI . SchemaObject > = { } ;
845848
846849 const required : string [ ] = [ ] ;
847850
@@ -853,7 +856,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
853856 } else {
854857 relType = field . type . array ? '_toManyRelationshipWithLinks' : '_toOneRelationshipWithLinks' ;
855858 }
856- relationships [ field . name ] = this . ref ( relType ) ;
859+ relationships [ field . name ] = this . wrapNullable ( this . ref ( relType ) , field . type . optional ) ;
857860 } else {
858861 attributes [ field . name ] = this . generateField ( field ) ;
859862 if (
@@ -911,48 +914,33 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
911914 }
912915
913916 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+ ) ;
919921 }
920922
921923 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 ) => {
941934 const fieldDecl = type . reference ?. ref ;
942- invariant ( fieldDecl ) ;
935+ invariant ( fieldDecl , `Type ${ t } is not a model reference` ) ;
943936 return this . ref ( fieldDecl ?. name ) ;
944- }
945- }
937+ } ) ;
946938 }
947939
948940 private ref ( type : string ) {
949941 return { $ref : `#/components/schemas/${ type } ` } ;
950942 }
951943
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-
956944 private parameter ( type : string ) {
957945 return { $ref : `#/components/parameters/${ type } ` } ;
958946 }
0 commit comments