diff --git a/lib/compile/cdsdl.d.ts b/lib/compile/cdsdl.d.ts new file mode 100644 index 0000000..e29ee27 --- /dev/null +++ b/lib/compile/cdsdl.d.ts @@ -0,0 +1,556 @@ +// generated using https://app.quicktype.io/ +// and https://raw.githubusercontent.com/oasis-tcs/odata-csdl-schemas/refs/heads/main/schemas/csdl.schema.json +// modifcations: +// - CSDLProperties.$Version changed to string, as that is how we use it +// - CSDLProperties.$EntityContrainer changed to string + +export type CSDL = { + readonly $schema: string; + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: CSDLPatternProperties; + readonly propertyNames: PropertyNames; + readonly properties: CSDLProperties; + readonly required: string[]; + readonly definitions: Definitions; +} + +export type Definitions = { + readonly Schema: Schema; + readonly EntityType: EntityType; + readonly ComplexType: ComplexType; + readonly Property: Property; + readonly NavigationProperty: NavigationProperty; + readonly EnumType: EnumType; + readonly TypeDefinition: TypeDefinition; + readonly Term: Term; + readonly Action: Ction; + readonly Function: Ction; + readonly EntityContainer: EntityContainer; + readonly EntitySet: EntitySet; + readonly Singleton: Singleton; + readonly ActionImport: ActionImport; + readonly FunctionImport: FunctionImport; + readonly NavigationPropertyBinding: NavigationPropertyBinding; + readonly Parameter: Parameter; + readonly ReturnType: ReturnType; + readonly MaxLength: MaxLength; + readonly Unicode: Unicode; + readonly Precision: MaxLength; + readonly Scale: Scale; + readonly SimpleIdentifier: QualifiedName; + readonly QualifiedName: QualifiedName; + readonly SRID: Srid; + readonly Annotation: Annotation; +} + +export type Ction = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: ActionProperties; + readonly required: string[]; +} + +export type ActionPatternProperties = { + readonly "^@": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$ = { + readonly $ref: string; +} + +export type ActionProperties = { + readonly $Kind: Version; + readonly $IsBound: Unicode; + readonly $EntitySetPath: Srid; + readonly $Parameter: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $ReturnType: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $IsComposable?: Unicode; +} + +export type Srid = { + readonly description: string; + readonly type: SRIDType; +} + +export enum SRIDType { + Integer = "integer", + String = "string", +} + +export type Unicode = { + readonly description: string; + readonly type: UnicodeType; + readonly default: boolean; + readonly examples: boolean[]; +} + +export enum UnicodeType { + Boolean = "boolean", +} + +export type Version = { + readonly description: string; + readonly enum: string[]; +} + +export type ActionImport = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: ActionImportProperties; + readonly required: string[]; +} + +export type ActionImportProperties = { + readonly $Action: Srid; + readonly $EntitySet: Srid; +} + +export type Annotation = { + readonly description: string; +} + +export type ComplexType = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ComplexTypePatternProperties; + readonly properties: ComplexTypeProperties; + readonly required: string[]; +} + +export type ComplexTypePatternProperties = { + readonly "^@": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly "^(_|\\p{L}|\\p{Nl})(_|\\p{L}|\\p{Nl}|\\p{Nd}|\\p{Mn}|\\p{Mc}|\\p{Pc}|\\p{Cf}){0,127}$": PurplePLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type PurplePLPNlPLPNlPNdPMnPMcPPCPCF0127$ = { + readonly oneOf: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$[]; +} + +export type ComplexTypeProperties = { + readonly $Kind: Version; + readonly $Abstract: Unicode; + readonly $OpenType: Unicode; + readonly $BaseType: Srid; +} + +export type EntityContainer = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ComplexTypePatternProperties; + readonly properties: EntityContainerProperties; + readonly required: string[]; +} + +export type EntityContainerProperties = { + readonly $Kind: Version; + readonly $Extends: Srid; +} + +export type EntitySet = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: EntitySetProperties; + readonly required: string[]; +} + +export type EntitySetProperties = { + readonly $Collection: Collection; + readonly $Type: Class; + readonly $NavigationPropertyBinding: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $IncludeInServiceDocument: Unicode; +} + +export type Collection = { + readonly description: string; + readonly enum: boolean[]; +} + +export type Class = { + readonly description: string; + readonly $ref: Ref; +} + +export enum Ref { + DefinitionsAnnotation = "#/definitions/Annotation", + DefinitionsQualifiedName = "#/definitions/QualifiedName", + DefinitionsSimpleIdentifier = "#/definitions/SimpleIdentifier", +} + +export type EntityType = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ComplexTypePatternProperties; + readonly properties: EntityTypeProperties; + readonly required: string[]; +} + +export type EntityTypeProperties = { + readonly $Kind: Version; + readonly $HasStream: Unicode; + readonly $Key: Key; + readonly $Abstract: Unicode; + readonly $OpenType: Unicode; + readonly $BaseType: Srid; +} + +export type Key = { + readonly description: string; + readonly type: string; + readonly items: KeyItems; +} + +export type KeyItems = { + readonly description: string; + readonly oneOf: ItemsOneOf[]; +} + +export type ItemsOneOf = { + readonly type: string; + readonly patternProperties?: OneOfPatternProperties; +} + +export type OneOfPatternProperties = { + readonly ".*": Srid; +} + +export type EnumType = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: EnumTypePatternProperties; + readonly properties: EnumTypeProperties; + readonly required: string[]; +} + +export type EnumTypePatternProperties = { + readonly "@": Class; + readonly "^(_|\\p{L}|\\p{Nl})(_|\\p{L}|\\p{Nl}|\\p{Nd}|\\p{Mn}|\\p{Mc}|\\p{Pc}|\\p{Cf}){0,127}$": Srid; +} + +export type EnumTypeProperties = { + readonly $Kind: Version; + readonly $IsFlags: Unicode; + readonly $UnderlyingType: UnderlyingType; +} + +export type UnderlyingType = { + readonly description: string; + readonly enum: string[]; + readonly default: string; +} + +export type FunctionImport = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: FunctionImportProperties; + readonly required: string[]; +} + +export type FunctionImportProperties = { + readonly $Function: Srid; + readonly $EntitySet: Srid; + readonly $IncludeInServiceDocument: Unicode; +} + +export type MaxLength = { + readonly description: string; + readonly type: SRIDType; + readonly minimum: number; +} + +export type NavigationProperty = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: NavigationPropertyPatternProperties; + readonly properties: NavigationPropertyProperties; + readonly required: string[]; +} + +export type NavigationPropertyPatternProperties = { + readonly "^@": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly "^\\$OnDelete@": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type NavigationPropertyProperties = { + readonly $Kind: Version; + readonly $Type: Class; + readonly $Collection: Unicode; + readonly $Nullable: Unicode; + readonly $Partner: Srid; + readonly $ContainsTarget: Unicode; + readonly $ReferentialConstraint: ReferentialConstraint; + readonly $OnDelete: Version; +} + +export type ReferentialConstraint = { + readonly type: string; + readonly patternProperties: ReferentialConstraintPatternProperties; +} + +export type ReferentialConstraintPatternProperties = { + readonly "@": Class; + readonly ".*": Srid; +} + +export type NavigationPropertyBinding = { + readonly description: string; + readonly type: string; + readonly patternProperties: OneOfPatternProperties; +} + +export type Parameter = { + readonly description: string; + readonly type: string; + readonly items: ParameterItems; +} + +export type ParameterItems = { + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: PropertyProperties; + readonly required: string[]; +} + +export type PropertyProperties = { + readonly $Name?: Class; + readonly $Type: Type; + readonly $Collection: Unicode; + readonly $Nullable: Unicode; + readonly $MaxLength: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Unicode: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Precision: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Scale: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $SRID: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Kind?: Version; + readonly $DefaultValue?: Annotation; + readonly $BaseTerm?: Srid; + readonly $AppliesTo?: AppliesTo; +} + +export type AppliesTo = { + readonly description: string; + readonly type: string; + readonly items: OneOf; +} + +export type OneOf = { + readonly type?: SRIDType; + readonly enum?: string[]; +} + +export type Type = { + readonly description: string; + readonly default: string; + readonly $ref: Ref; +} + +export type Property = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: PropertyProperties; +} + +export type QualifiedName = { + readonly description: string; + readonly type: SRIDType; + readonly pattern: string; +} + +export type ReturnType = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties?: ReturnTypeProperties; +} + +export type ReturnTypeProperties = { + readonly $Type?: Type; + readonly $Collection?: Unicode; + readonly $Nullable?: Unicode; + readonly $MaxLength?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Unicode?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Precision?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Scale?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $SRID?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Include?: Include; + readonly $IncludeAnnotations?: IncludeAnnotations; +} + +export type Include = { + readonly description: string; + readonly type: string; + readonly items: IncludeItems; +} + +export type IncludeItems = { + readonly type: string; + readonly additionalProperties: boolean; + readonly properties: PurpleProperties; + readonly patternProperties: ActionPatternProperties; + readonly required: string[]; +} + +export type PurpleProperties = { + readonly $Namespace: Class; + readonly $Alias: Class; +} + +export type IncludeAnnotations = { + readonly description: string; + readonly type: string; + readonly items: IncludeAnnotationsItems; +} + +export type IncludeAnnotationsItems = { + readonly type: string; + readonly additionalProperties: boolean; + readonly properties: FluffyProperties; + readonly required: string[]; +} + +export type FluffyProperties = { + readonly $TermNamespace: Class; + readonly $TargetNamespace: Class; + readonly $Qualifier: Class; +} + +export type Scale = { + readonly description: string; + readonly oneOf: OneOf[]; +} + +export type Schema = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: SchemaPatternProperties; + readonly properties: SchemaProperties; +} + +export type SchemaPatternProperties = { + readonly "^@": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly "^(_|\\p{L}|\\p{Nl})(_|\\p{L}|\\p{Nl}|\\p{Nd}|\\p{Mn}|\\p{Mc}|\\p{Pc}|\\p{Cf}){0,127}$": FluffyPLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type FluffyPLPNlPLPNlPNdPMnPMcPPCPCF0127$ = { + readonly oneOf: PLPNlPLPNlPNdPMnPMcPPCPCF0127$_OneOf[]; +} + +export type PLPNlPLPNlPNdPMnPMcPPCPCF0127$_OneOf = { + readonly $ref?: string; + readonly description?: string; + readonly type?: string; + readonly items?: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type SchemaProperties = { + readonly $Alias: Class; + readonly $Annotations: Annotations; +} + +export type Annotations = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: AnnotationsPatternProperties; +} + +export type AnnotationsPatternProperties = { + readonly "^[^$]": ReturnType; +} + +export type Singleton = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: SingletonProperties; + readonly required: string[]; +} + +export type SingletonProperties = { + readonly $Type: Class; + readonly $Nullable: Unicode; + readonly $NavigationPropertyBinding: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type Term = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: PropertyProperties; + readonly required: string[]; +} + +export type TypeDefinition = { + readonly description: string; + readonly type: string; + readonly additionalProperties: boolean; + readonly patternProperties: ActionPatternProperties; + readonly properties: TypeDefinitionProperties; + readonly required: string[]; +} + +export type TypeDefinitionProperties = { + readonly $Kind: Version; + readonly $UnderlyingType: Srid; + readonly $MaxLength: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Unicode: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Precision: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $Scale: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; + readonly $SRID: PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type CSDLPatternProperties = { + readonly "^(_|\\p{L}|\\p{Nl})(_|\\p{L}|\\p{Nl}|\\p{Nd}|\\p{Mn}|\\p{Mc}|\\p{Pc}|\\p{Cf}){0,127}(\\.(_|\\p{L}|\\p{Nl})(_|\\p{L}|\\p{Nl}|\\p{Nd}|\\p{Mn}|\\p{Mc}|\\p{Pc}|\\p{Cf}){0,127})*$": PLPNlPLPNlPNdPMnPMcPPCPCF0127__PLPNlPLPNlPNdPMnPMcPPCPCF0127$; +} + +export type CSDLProperties = { + $Version: string; + readonly $EntityContainer: string; + readonly $Reference: Reference; +} + +export type Reference = { + readonly description: string; + readonly type: string; + readonly patternProperties: ReferencePatternProperties; +} + +export type ReferencePatternProperties = { + readonly ".*": ReturnType; +} + +export type PropertyNames = { + readonly maxLength: number; +} + +// Converts JSON strings to/from your types +export class Convert { + public static toCSDL(json: string): CSDL { + return JSON.parse(json); + } + + public static CSDLToJson(value: CSDL): string { + return JSON.stringify(value); + } +} diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 1b4b622..80544e5 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -22,6 +22,15 @@ const DEBUG = cds.debug('openapi'); // Initialize cds.debug with the 'openapi' // - InsertRestrictions/NonInsertableNavigationProperties // - see //TODO comments below + +/** + * @typedef {{ + * [K in keyof typeof terms]: { + * [P in typeof terms[K][number]]: string; + * } + * } & { Common: { Label: string } }} Vocabulary + */ + const SUFFIX = { read: "", create: "-create", @@ -73,10 +82,24 @@ const ER_ANNOTATIONS = Object.freeze( '@EntityRelationship.referencesWithConstantIds': 'x-entity-relationship-references-with-constant-ids' }); + +const terms = { + Authorization: /**@type{const}*/(['Authorizations', 'SecuritySchemes']), + Capabilities: /**@type{const}*/([ + 'BatchSupport', 'BatchSupported', 'ChangeTracking', 'CountRestrictions', 'DeleteRestrictions', 'DeepUpdateSupport', 'ExpandRestrictions', + 'FilterRestrictions', 'IndexableByKey', 'InsertRestrictions', 'KeyAsSegmentSupported', 'NavigationRestrictions', 'OperationRestrictions', + 'ReadRestrictions', 'SearchRestrictions', 'SelectSupport', 'SkipSupported', 'SortRestrictions', 'TopSupported', 'UpdateRestrictions']), + Core: /**@type{const}*/([ + 'AcceptableMediaTypes', 'Computed', 'ComputedDefaultValue', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription', + 'OptionalParameter', 'Permissions', 'SchemaVersion']), + JSON: /**@type{const}*/(['Schema']), + Validation: /**@type{const}*/(['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern']) +}; + /** * Construct an OpenAPI description from a CSDL document - * @param {object} csdl CSDL document - * @param {object} options Optional parameters + * @param {import('./types').CSDL} csdl CSDL document + * @param {{ url?: string, servers?: object, odataVersion?: string, scheme?: string, host?: string, basePath?: string, diagram?: boolean, maxLevels?: number }} options Optional parameters * @return {object} OpenAPI description */ module.exports.csdl2openapi = function ( @@ -103,6 +126,8 @@ module.exports.csdl2openapi = function ( const alias = {}; const namespace = { 'Edm': 'Edm' }; const namespaceUrl = {}; + /** @type {Vocabulary} */ + // @ts-expect-error type will be fixed by reference in preProcess const voc = {}; /** @type {{ list: { namespace:string, name: string, suffix: string }[], used: Record }} */ const requiredSchemas = { list: [], used: {} }; @@ -253,18 +278,18 @@ module.exports.csdl2openapi = function ( /** * Collect model info for easier lookup - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @param {object} boundOverloads Map of action/function names to bound overloads * @param {object} derivedTypes Map of type names to derived types * @param {object} alias Map of namespace or alias to alias - * @param {object} namespace Map of namespace or alias to namespace + * @param {Record} namespace Map of namespace or alias to namespace * @param {object} namespaceUrl Map of namespace to reference URL - * @param {object} voc Map of vocabularies and terms + * @param {Vocabulary} voc Map of vocabularies and terms */ function preProcess(csdl, boundOverloads, derivedTypes, alias, namespace, namespaceUrl, voc) { - Object.keys(csdl.$Reference || {}).forEach(url => { + Object.keys(csdl.$Reference ?? {}).forEach(url => { const reference = csdl.$Reference[url]; - (reference.$Include || []).forEach(include => { + (reference.$Include ?? []).forEach(include => { const qualifier = include.$Alias || include.$Namespace; alias[include.$Namespace] = qualifier; namespace[qualifier] = include.$Namespace; @@ -365,21 +390,10 @@ module.exports.csdl2openapi = function ( /** * Construct map of qualified term names - * @param {object} voc Map of vocabularies and terms + * @param {Vocabulary} voc Map of vocabularies and terms * @param {object} alias Map of namespace or alias to alias */ function getVocabularies(voc, alias) { - const terms = { - Authorization: ['Authorizations', 'SecuritySchemes'], - Capabilities: ['BatchSupport', 'BatchSupported', 'ChangeTracking', 'CountRestrictions', 'DeleteRestrictions', 'DeepUpdateSupport', 'ExpandRestrictions', - 'FilterRestrictions', 'IndexableByKey', 'InsertRestrictions', 'KeyAsSegmentSupported', 'NavigationRestrictions', 'OperationRestrictions', - 'ReadRestrictions', 'SearchRestrictions', 'SelectSupport', 'SkipSupported', 'SortRestrictions', 'TopSupported', 'UpdateRestrictions'], - Core: ['AcceptableMediaTypes', 'Computed', 'ComputedDefaultValue', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription', - 'OptionalParameter', 'Permissions', 'SchemaVersion'], - JSON: ['Schema'], - Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern'] - }; - Object.keys(terms).forEach(vocab => { voc[vocab] = {}; terms[vocab].forEach(term => { @@ -395,7 +409,7 @@ module.exports.csdl2openapi = function ( /** * Construct the Info Object - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @param {object} entityContainer Entity Container object * @return {object} Info Object */ @@ -429,7 +443,7 @@ module.exports.csdl2openapi = function ( /** * Construct the externalDocs Object - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @return {object} externalDocs Object */ function getExternalDoc(csdl) { @@ -447,7 +461,7 @@ module.exports.csdl2openapi = function ( /** * Construct resource diagram using web service at https://yuml.me - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @param {object} entityContainer Entity Container object * @return {string} resource diagram */ @@ -528,7 +542,7 @@ module.exports.csdl2openapi = function ( /** * Diagram representation of property cardinality * @param {object} typedElement Typed model element, e.g. property - * @return {string} cardinality + * @return {'*' | '0..1' | ''} cardinality */ function cardinality(typedElement) { return typedElement.$Collection ? '*' : (typedElement.$Nullable ? '0..1' : ''); @@ -571,7 +585,7 @@ module.exports.csdl2openapi = function ( /** * Construct the short text - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @param {object} entityContainer Entity Container object * @return {string} short text */ @@ -595,7 +609,7 @@ module.exports.csdl2openapi = function ( * Construct an array of Server Objects * @param {object} serviceRoot The service root * @param {object} serversObject Input servers object - * @return {Array} The list of servers + * @return {{url: string}[]} The list of servers */ function getServers(serviceRoot, serversObject) { let servers; @@ -640,9 +654,10 @@ module.exports.csdl2openapi = function ( /** * Construct the Paths Object from the entity container * @param {object} container Entity container - * @return {object} Paths Object + * @return {import('./types').Paths} Paths Object */ function getPaths(container) { + /** @type {import('./types').Paths} */ const paths = {}; const resources = Object.keys(container).filter(name => isIdentifier(name)); resources.forEach(name => { @@ -667,7 +682,7 @@ module.exports.csdl2openapi = function ( /** * Add path and Path Item Object for a navigation segment - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} prefix Prefix for path * @param {Array} prefixParameters Parameter Objects for prefix * @param {object} element Model element of navigation segment @@ -721,9 +736,9 @@ module.exports.csdl2openapi = function ( * @return Navigation property restrictions of navigation segment */ function navigationPropertyRestrictions(root, navigationPath) { - const navigationRestrictions = root[voc.Capabilities.NavigationRestrictions] || {}; - return (navigationRestrictions.RestrictedProperties || []).find(item => navigationPropertyPath(item.NavigationProperty) == navigationPath) - || {}; + const navigationRestrictions = root[voc.Capabilities.NavigationRestrictions] ?? {}; + return (navigationRestrictions.RestrictedProperties ?? []).find(item => navigationPropertyPath(item.NavigationProperty) == navigationPath) + ?? {}; } /** @@ -737,7 +752,7 @@ module.exports.csdl2openapi = function ( const prefix = navigationPath.length === 0 ? '' : navigationPath + '/' const from = prefix.length const nonExpandable = [] - for (const path of (expandRestrictions.NonExpandableProperties || [])) { + for (const path of (expandRestrictions.NonExpandableProperties ?? [])) { if (path.startsWith(prefix)) { nonExpandable.push(path.substring(from)) } @@ -747,7 +762,7 @@ module.exports.csdl2openapi = function ( /** * Add path and Path Item Object for a navigation segment with key - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} prefix Prefix for path * @param {Array} prefixParameters Parameter Objects for prefix * @param {object} element Model element of navigation segment @@ -892,6 +907,7 @@ module.exports.csdl2openapi = function ( }; const deltaSupported = element[voc.Capabilities.ChangeTracking] && element[voc.Capabilities.ChangeTracking].Supported; if (!byKey && deltaSupported) { + // @ts-expect-error - set above operation.responses[200].content['application/json'].schema.properties['@odata.deltaLink'] = { type: 'string', example: basePath + '/' + name + '?$deltatoken=opaque server-generated token for fetching the delta' @@ -1546,7 +1562,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Add path and Path Item Object for actions and functions bound to the element - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} prefix Prefix for path * @param {Array} prefixParameters Parameter Objects for prefix * @param {object} element Model element the operations are bound to @@ -1569,7 +1585,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Add path and Path Item Object for an action import - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} name Name of action import * @param {object} child Action import object */ @@ -1580,7 +1596,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Add path and Path Item Object for action overload - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} prefix Prefix for path * @param {Array} prefixParameters Parameter Objects for prefix * @param {string} actionName Qualified name of function @@ -1640,7 +1656,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Add path and Path Item Object for function overload - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {string} prefix Prefix for path * @param {Array} prefixParameters Parameter Objects for prefix * @param {string} functionName Qualified name of function @@ -1737,7 +1753,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Add path and Path Item Object for batch requests - * @param {object} paths Paths Object to augment + * @param {import('./types').Paths} paths Paths Object to augment * @param {object} container Entity container */ function pathItemBatch(paths, container) { @@ -1747,8 +1763,8 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot const firstEntitySet = Object.keys(container).filter(child => isIdentifier(child) && container[child].$Collection)[0]; paths['/$batch'] = { post: { - summary: batchSupport[voc.Core.Description] || 'Sends a group of requests', - description: (batchSupport[voc.Core.LongDescription] || 'Group multiple requests into a single request payload, see ' + summary: batchSupport[voc.Core.Description] ?? 'Sends a group of requests', + description: (batchSupport[voc.Core.LongDescription] ?? 'Group multiple requests into a single request payload, see ' + '[Batch Requests](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_BatchRequests).') + '\n\n*Please note that "Try it out" is not supported for this request.*', tags: ['Batch Requests'], @@ -1776,6 +1792,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot } } }; + // @ts-expect-error - set above paths['/$batch'].post.responses[csdl.$Version < '4.0' ? 202 : 200] = { description: 'Batch response', content: { @@ -1801,11 +1818,14 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot * @param {string} description Description * @param {object} type Response type object * @param {array} errors Array of operation-specific status codes with descriptions + * @returns {Record} */ function response(code, description, type, errors, isCount = true) { - const r = {}; - r[code] = { - description: description + /** @type {Record} */ + const r = { + [code]: { + description: description + } }; let CountPropertyObj = { [csdl.$Version > '4.0' ? '@count' : '@odata.count']: ref('count') }; if (code != 204) { @@ -1815,6 +1835,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot }; if (type.$Collection) { + // @ts-ignore - not undefined, we set it above r[code].content['application/json'].schema = { type: 'object', title: 'Collection of ' + nameParts(type.$Type ? type.$Type : 'Edm.String').name, @@ -1832,10 +1853,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot type.$Type, )) ) { + // @ts-expect-error - set above r[code].content['application/json'].schema = { type: "object", properties: { value: s } }; } else { + // @ts-expect-error - set above r[code].content['application/json'].schema = s; } } @@ -1860,7 +1883,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Construct the Components Object from the types of the CSDL document - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @param {object} entityContainer Entity Container object * @return {object} Components Object */ @@ -1890,7 +1913,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot /** * Construct Schema Objects from the types of the CSDL document - * @param {object} csdl CSDL document + * @param {import('./types').CSDL} csdl CSDL document * @return {object} Map of Schema Objects */ function getSchemas(csdl) { diff --git a/lib/compile/types.d.ts b/lib/compile/types.d.ts index 1a8f7b9..1aeb577 100644 --- a/lib/compile/types.d.ts +++ b/lib/compile/types.d.ts @@ -26,6 +26,14 @@ type ArraySchema = { items: Schema } +type ObjectSchema = { + type: 'object', + properties: { value: any } & {}, +} + +// for responses that only contain meta information +type EmptySchema = {} + type Meta = { nullable?: boolean default?: unknown @@ -34,7 +42,7 @@ type Meta = { '$ref'?: unknown } -type SingleSchema = (StringSchema | NumberSchema | BooleanSchema | ArraySchema) & Meta +type SingleSchema = (StringSchema | NumberSchema | BooleanSchema | ArraySchema | ObjectSchema | EmptySchema) & Meta type AnyOf = { anyOf: Schema[] } & Meta type AllOf = { allOf: Schema[] } & Meta @@ -43,8 +51,56 @@ type MultiSchema = AnyOf | AllOf export type Schema = (SingleSchema | MultiSchema) - export type TargetRestrictions = { Countable?: boolean Expandable?: boolean -} \ No newline at end of file +} + +// despite of how CSDL is defined in the standard, +// we assume to be working with its .properties field +// throughout our conversion +import type { CSDL as CSDL_ } from './cdsdl'; +export type CSDL = CSDL_['properties']; + + + +export type PathItem = { + get?: Request, + post?: Request, + put?: Request, + patch?: Request, + delete?: Request, +} | Request + +type MIMEType = 'multipart/mixed' | 'application/json' + +type Request = { + summary?: string, + description?: string, + tags?: string[], + parameters?: string[], + responses?: Record, + requestBody?: RequestBody, +} + +type Response = { + required?: boolean, + description?: string, + content?: {[mime in MIMEType]?: { + schema?: SingleSchema & { title?: string }, + example?: string + }}, + responses?: Record, + $ref?: string +} + +type RequestBody = { + required: boolean, + description: string, + content: Record, +} + +export type Paths = { [path: string]: PathItem }