@@ -106,7 +106,107 @@ module.exports.csdl2openapi = function (
106
106
if ( externalDocs && Object . keys ( externalDocs ) . length > 0 ) {
107
107
openapi . externalDocs = externalDocs ;
108
108
}
109
+ const extensions = getExtensions ( csdl , 'root' ) ;
110
+ if ( extensions && Object . keys ( extensions ) . length > 0 ) {
111
+ Object . assign ( openapi , extensions ) ;
112
+ }
113
+
114
+ // function to read @OpenAPI .Extensions and get them in the generated openAPI document
115
+ function getExtensions ( csdl , level ) {
116
+ let extensionObj = { } ;
117
+ let containerSchema = { } ;
118
+ if ( level === 'root' ) {
119
+ const namespace = csdl . $EntityContainer ? nameParts ( csdl . $EntityContainer ) . qualifier : null ;
120
+ containerSchema = csdl . $EntityContainer ? csdl [ namespace ] : { } ;
121
+ }
122
+ else if ( level === 'schema' || level === 'operation' ) {
123
+ containerSchema = csdl ;
124
+ }
125
+
126
+ for ( const [ key , value ] of Object . entries ( containerSchema ) ) {
127
+ if ( key . startsWith ( '@OpenAPI.Extensions' ) ) {
128
+ const annotationProperties = key . split ( '@OpenAPI.Extensions.' ) [ 1 ] ;
129
+ const keys = annotationProperties . split ( '.' ) ;
130
+ if ( ! keys [ 0 ] . startsWith ( "x-sap-" ) ) {
131
+ keys [ 0 ] = ( keys [ 0 ] . startsWith ( "sap-" ) ? "x-" : "x-sap-" ) + keys [ 0 ] ;
132
+ }
133
+ if ( keys . length === 1 ) {
134
+ extensionObj [ keys [ 0 ] ] = value ;
135
+ } else {
136
+ nestedAnnotation ( extensionObj , keys [ 0 ] , keys , value ) ;
137
+ }
138
+ }
139
+ }
140
+ let extensionEnums = {
141
+ "x-sap-compliance-level" : { allowedValues : [ "sap:base:v1" , "sap:core:v1" , "sap:core:v2" ] } ,
142
+ "x-sap-api-type" : { allowedValues : [ "ODATA" , "ODATAV4" , "REST" , "SOAP" ] } ,
143
+ "x-sap-direction" : { allowedValues : [ "inbound" , "outbound" , "mixed" ] , default : "inbound" } ,
144
+ "x-sap-dpp-entity-semantics" : { allowedValues : [ "sap:DataSubject" , "sap:DataSubjectDetails" , "sap:Other" ] } ,
145
+ "x-sap-dpp-field-semantics" : { allowedValues : [ "sap:DataSubjectID" , "sap:ConsentID" , "sap:PurposeID" , "sap:ContractRelatedID" , "sap:LegalEntityID" , "sap:DataControllerID" , "sap:UserID" , "sap:EndOfBusinessDate" , "sap:BlockingDate" , "sap:EndOfRetentionDate" ] } ,
146
+ } ;
147
+ checkForExtentionEnums ( extensionObj , extensionEnums ) ;
148
+
149
+ let extenstionSchema = {
150
+ "x-sap-stateInfo" : [ 'state' , 'deprecationDate' , 'decomissionedDate' , 'link' ] ,
151
+ "x-sap-ext-overview" : [ 'name' , 'values' ] ,
152
+ "x-sap-deprecated-operation" : [ 'deprecationDate' , 'successorOperationRef' , "successorOperationId" ] ,
153
+ "x-sap-odm-semantic-key" : [ 'name' , 'values' ] ,
154
+ } ;
155
+
156
+ checkForExtentionSchema ( extensionObj , extenstionSchema ) ;
157
+ return extensionObj ;
158
+ }
159
+
160
+ function checkForExtentionEnums ( extensionObj , extensionEnums ) {
161
+ for ( const [ key , value ] of Object . entries ( extensionObj ) ) {
162
+ if ( extensionEnums [ key ] && extensionEnums [ key ] . allowedValues && ! extensionEnums [ key ] . allowedValues . includes ( value ) ) {
163
+ if ( extensionEnums [ key ] . default ) {
164
+ extensionObj [ key ] = extensionEnums [ key ] . default ;
165
+ }
166
+ else {
167
+ delete extensionObj [ key ] ;
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ function checkForExtentionSchema ( extensionObj , extenstionSchema ) {
174
+ for ( const [ key , value ] of Object . entries ( extensionObj ) ) {
175
+ if ( extenstionSchema [ key ] ) {
176
+ if ( Array . isArray ( value ) ) {
177
+ extensionObj [ key ] = value . filter ( ( v ) => extenstionSchema [ key ] . includes ( v ) ) ;
178
+ } else if ( typeof value === "object" && value !== null ) {
179
+ for ( const field in value ) {
180
+ if ( ! extenstionSchema [ key ] . includes ( field ) ) {
181
+ delete extensionObj [ key ] [ field ] ;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+
190
+ function nestedAnnotation ( resObj , openapiProperty , keys , value ) {
191
+ if ( resObj [ openapiProperty ] === undefined ) {
192
+ resObj [ openapiProperty ] = { } ;
193
+ }
194
+
195
+ let node = resObj [ openapiProperty ] ;
196
+
197
+ // traverse the annotation property and define the objects if they're not defined
198
+ for ( let nestedIndex = 1 ; nestedIndex < keys . length - 1 ; nestedIndex ++ ) {
199
+ const nestedElement = keys [ nestedIndex ] ;
200
+ if ( node [ nestedElement ] === undefined ) {
201
+ node [ nestedElement ] = { } ;
202
+ }
203
+ node = node [ nestedElement ] ;
204
+ }
109
205
206
+ // set value annotation property
207
+ node [ keys [ keys . length - 1 ] ] = value ;
208
+ }
209
+
110
210
if ( ! csdl . $EntityContainer ) {
111
211
delete openapi . servers ;
112
212
delete openapi . tags ;
@@ -1466,6 +1566,10 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1466
1566
: response ( 204 , "Success" , undefined , overload [ voc . Capabilities . OperationRestrictions ] ?. ErrorResponses ) ,
1467
1567
}
1468
1568
} ;
1569
+ const actionExtension = getExtensions ( overload , 'operation' ) ;
1570
+ if ( Object . keys ( actionExtension ) . length > 0 ) {
1571
+ Object . assign ( pathItem . post , actionExtension ) ;
1572
+ }
1469
1573
const description = actionImport [ voc . Core . LongDescription ] || overload [ voc . Core . LongDescription ] ;
1470
1574
if ( description ) pathItem . post . description = description ;
1471
1575
if ( prefixParameters . length > 0 ) pathItem . post . parameters = [ ...prefixParameters ] ;
@@ -1576,6 +1680,10 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1576
1680
responses : response ( 200 , "Success" , overload . $ReturnType , overload [ voc . Capabilities . OperationRestrictions ] ?. ErrorResponses ) ,
1577
1681
}
1578
1682
} ;
1683
+ const functionExtension = getExtensions ( overload , 'operation' ) ;
1684
+ if ( Object . keys ( functionExtension ) . length > 0 ) {
1685
+ Object . assign ( pathItem . get , functionExtension ) ;
1686
+ }
1579
1687
const iDescription = functionImport [ voc . Core . LongDescription ] || overload [ voc . Core . LongDescription ] ;
1580
1688
if ( iDescription ) pathItem . get . description = iDescription ;
1581
1689
customParameters ( pathItem . get , overload [ voc . Capabilities . OperationRestrictions ] || { } ) ;
@@ -1759,6 +1867,23 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1759
1867
break ;
1760
1868
}
1761
1869
}
1870
+
1871
+ // Add @OpenAPI .Extensions at entity level to schema object
1872
+ Object . keys ( csdl ) . filter ( name => isIdentifier ( name ) ) . forEach ( namespace => {
1873
+ const schema = csdl [ namespace ] ;
1874
+ Object . keys ( schema ) . filter ( name => isIdentifier ( name ) ) . forEach ( name => {
1875
+ const type = schema [ name ] ;
1876
+ if ( type . $Kind === 'EntityType' || type . $Kind === 'ComplexType' ) {
1877
+ const schemaName = namespace + "." + name + SUFFIX . read ;
1878
+ const extensions = getExtensions ( type , 'schema' ) ;
1879
+ if ( Object . keys ( extensions ) . length > 0 ) {
1880
+ unordered [ schemaName ] = unordered [ schemaName ] || { } ;
1881
+ Object . assign ( unordered [ schemaName ] , extensions ) ;
1882
+ }
1883
+ }
1884
+ } ) ;
1885
+ } ) ;
1886
+
1762
1887
const ordered = { } ;
1763
1888
for ( const name of Object . keys ( unordered ) . sort ( ) ) {
1764
1889
ordered [ name ] = unordered [ name ] ;
0 commit comments