Skip to content

Commit 8a9eac9

Browse files
authored
Merge pull request #22 from homestar9/patch/parser-fixes-2022-11
Parser Fixes and Coldbox RESTful App Tests
2 parents cd13196 + df0d5bb commit 8a9eac9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1138
-51
lines changed

models/OpenAPI/Parser.cfc

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,31 @@ component name="OpenAPIParser" accessors="true" {
133133
} else if( isStruct( DocItem ) ) {
134134

135135
//handle top-level values, if they exist
136-
if( structKeyExists( DocItem, "$ref" ) ) return fetchDocumentReference( DocItem[ "$ref" ] );
136+
if( structKeyExists( DocItem, "$ref" ) ) {
137+
// special handling for double pound signs
138+
if( left( DocItem[ "$ref" ], 2 ) == chr( 35 ) & chr( 35 ) ) {
139+
DocItem[ "$ref" ] = right( DocItem[ "$ref" ], ( len( DocItem[ "$ref" ] ) - 1 ) );
140+
return DocItem;
141+
}
142+
return fetchDocumentReference( DocItem[ "$ref" ] );
143+
}
137144

138145
for( var key in DocItem){
139146

147+
// If `DocItem[ key ]` is an instance of Parser, we need to flattin it to a CFML struct
148+
if (
149+
isStruct( DocItem[ key ] ) &&
150+
findNoCase( "Parser", getMetaData( DocItem[ key ] ).name )
151+
) {
152+
DocItem[ key ] = DocItem[ key ].getNormalizedDocument();
153+
}
154+
140155
if (
141-
isStruct( DocItem[ key ] )
142-
&&
156+
isStruct( DocItem[ key ] ) &&
143157
structKeyExists( DocItem[ key ], "$ref" )
144158
) {
145159

146-
DocItem[ key ] = fetchDocumentReference( DocItem[ key ][ "$ref" ] );
160+
DocItem[ key ] = parseDocumentReferences( fetchDocumentReference( DocItem[ key ][ "$ref" ] ) );
147161

148162
} else if( isStruct( DocItem[ key ] ) || isArray( DocItem[ key ] ) ){
149163

@@ -160,45 +174,39 @@ component name="OpenAPIParser" accessors="true" {
160174

161175

162176
/**
163-
* Parses API Document $allOf notations recursively
177+
* Parses API Document $extend notations recursively
164178
*
165179
* @param APIDoc The struct representation of the API Document
166180
* @param [XPath] The XPath to zoom the parsed document to during recursion
167181
**/
168182
public function parseDocumentInheritance( required any DocItem ){
169-
183+
184+
// If `DocItem` is an instance of Parser, we need to flattin it to a CFML struct
185+
if (
186+
isStruct( DocItem ) &&
187+
findNoCase( "Parser", getMetaData( DocItem ).name )
188+
) {
189+
DocItem = DocItem.getNormalizedDocument();
190+
}
191+
170192
if( isArray( DocItem ) ) {
171193
for( var i = 1; i <= arrayLen( DocItem ); i++){
172194
DocItem[ i ] = parseDocumentInheritance( DocItem[ i ] );
173195
}
174-
} else if( isStruct( DocItem ) ) {
175-
176-
var compositionKeys = [ "$allOf", "$oneOf" ];
177-
178-
for( var composition in compositionKeys ){
196+
} else if( isStruct( DocItem ) ) {
179197

180-
// handle top-level extension
181-
if(
182-
structKeyExists( DocItem, composition ) &&
183-
isArray( DocItem[ composition ] )
184-
) {
185-
return extendObject( DocItem[ composition ] );
186-
}
187-
188-
for( var key in DocItem){
198+
// handle top-level extension
199+
if( structKeyExists( DocItem, "$extend" ) ) {
200+
return extendObject( parseDocumentInheritance( DocItem[ "$extend" ] ) );
201+
}
189202

190-
if (
191-
isStruct( DocItem[ key ] ) &&
192-
structKeyExists( DocItem[ key ], composition ) &&
193-
isArray( DocItem[ key ][ composition ] )
194-
) {
195-
DocItem[ key ] = parseDocumentReferences( extendObject( DocItem[ key ][ composition ] ) );
196-
} else if( isStruct( DocItem[ key ] ) || isArray( DocItem[ key ] ) ){
197-
DocItem[ key ] = parseDocumentInheritance( parseDocumentReferences( DocItem[ key ] ) );
198-
}
203+
for( var key in DocItem ){
204+
205+
if( isStruct( DocItem[ key ] ) || isArray( DocItem[ key ] ) ){
206+
DocItem[ key ] = parseDocumentInheritance( DocItem[ key ] );
207+
}
199208

200-
}
201-
}
209+
}
202210

203211
}
204212

@@ -214,19 +222,12 @@ component name="OpenAPIParser" accessors="true" {
214222
* @objects
215223
*/
216224
function extendObject( array objects ) {
217-
var output = {
218-
"type": "object"
219-
};
225+
226+
var output = {};
220227
objects.each( function( item, index ) {
221228
if ( isStruct( item ) ) {
222-
223-
// If `item` is an instance of Parser, we need to flattin it to a CFML struct
224-
if ( findNoCase( "Parser", getMetaData( item ).name ) ) {
225-
item = item.getNormalizedDocument();
226-
}
227-
228229
item.each( function( key, value ) {
229-
230+
230231
if (
231232
output.keyExists( key ) &&
232233
isStruct( output[ key ] )
@@ -266,8 +267,11 @@ component name="OpenAPIParser" accessors="true" {
266267
**/
267268
private function fetchDocumentReference( required string $ref ){
268269

270+
// double pound ## means we want to preserve the swagger $ref pointer (just remove the extra #)
271+
if( left( $ref, 2 ) == chr( 35 ) & chr( 35 ) ){
272+
return { "$ref": right( $ref, ( len( $ref ) - 1 ) ) };
269273
//resolve internal refrences before looking for externals
270-
if( left( $ref, 1 ) == chr( 35 )){
274+
} else if( left( $ref, 1 ) == chr( 35 )){
271275
var FilePath = "";
272276
var XPath = right( $ref, len( $ref ) - 1 );
273277
} else {
@@ -314,7 +318,12 @@ component name="OpenAPIParser" accessors="true" {
314318

315319
} catch( any e ){
316320

317-
throw(
321+
// if this is a known exception or occured via recursion, rethrow the exception so the user knows which JSON file triggered it
322+
if ( listFindNoCase( "SwaggerSDK.ParserException,CBSwagger.InvalidReferenceDocumentException", e.type ) ) {
323+
rethrow;
324+
}
325+
326+
throw(
318327
type="CBSwagger.InvalidReferenceDocumentException",
319328
message="The $ref file pointer of #$ref# could not be loaded and parsed as a valid object. If your $ref file content is an array, please nest the array within an object as a named key."
320329
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"summary": "Response",
3+
"description": "A generic response",
4+
"value": {
5+
"error": false,
6+
"data": {},
7+
"messages": []
8+
}
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"maxRows": {
5+
"description": "Maximum number of rows returned.",
6+
"type": "integer"
7+
},
8+
"totalPages": {
9+
"description": "Total pages available.",
10+
"type": "integer"
11+
},
12+
"offset": {
13+
"description": "Number of rows skipped.",
14+
"type": "integer"
15+
},
16+
"page": {
17+
"description": "Current page number.",
18+
"type": "integer"
19+
},
20+
"totalRecords": {
21+
"description": "Total number of records available.",
22+
"type": "integer"
23+
}
24+
}
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"error": {
5+
"description": "Flag to indicate an error.",
6+
"type": "boolean"
7+
},
8+
"messages": {
9+
"description": "An array of messages related to the request.",
10+
"type": "array",
11+
"items": {
12+
"type": "string"
13+
}
14+
},
15+
"data": {
16+
"description": "The data packet",
17+
"type": "object",
18+
"properties": {}
19+
}
20+
}
21+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"openapi": "3.0.2",
3+
"info": {
4+
"title": "Coldbox Rest - OpenAPI 3.0",
5+
"description": "This is a sample coldbox rest application",
6+
"termsOfService": "",
7+
"contact": {
8+
"email": "[email protected]"
9+
},
10+
"license": {
11+
"name": "Apache 2.0",
12+
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
13+
},
14+
"version": "1.0.17"
15+
},
16+
"externalDocs": {
17+
"description": "Find out more about Swagger",
18+
"url": "http://swagger.io"
19+
},
20+
"servers": [
21+
{
22+
"url": "/api/v3"
23+
},
24+
{
25+
"url": "/api/v2"
26+
}
27+
],
28+
"tags": [
29+
{
30+
"name": "users",
31+
"description": "Operations about users"
32+
},
33+
{
34+
"name": "posts",
35+
"description": "Operations about posts"
36+
},
37+
{
38+
"name": "media",
39+
"description": "Operations about media"
40+
}
41+
],
42+
"components": {
43+
"securitySchemes": {
44+
"APIKey" : {
45+
"type" : "apiKey",
46+
"description" : "Bearer Token",
47+
"name" : "Authorization: Bearer",
48+
"in" : "header"
49+
}
50+
},
51+
"schemas": {
52+
"Pagination": { "$ref": "_schemas/pagination.json" },
53+
"User": { "$ref": "paths/users/_schemas/user.json" },
54+
"Post": { "$ref": "paths/posts/_schemas/post.json" },
55+
"PostBody": { "$ref": "paths/posts/_schemas/postBody.json" },
56+
"Media": { "$ref": "paths/media/_schemas/media.json" },
57+
"BookMedia": { "$ref": "paths/media/_schemas/bookMedia.json" },
58+
"MusicMedia": { "$ref": "paths/media/_schemas/musicMedia.json" },
59+
"BookMediaBody": { "$ref": "paths/media/_schemas/bookMediaBody.json" },
60+
"MusicMediaBody": { "$ref": "paths/media/_schemas/musicMediaBody.json" }
61+
},
62+
"requestBodies": {
63+
"PostBody": { "$ref": "paths/posts/_bodies/postBody.json" },
64+
"MediaBody": { "$ref": "paths/media/_bodies/mediaBody.json" }
65+
}
66+
},
67+
"paths": {
68+
"$extend": [
69+
{ "$ref": "paths/users/users.json" },
70+
{ "$ref": "paths/posts/posts.json" },
71+
{ "$ref": "paths/media/media.json" }
72+
]
73+
}
74+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"description": "Media to Create",
3+
"required": true,
4+
"content": {
5+
"application/json": {
6+
"schema": {
7+
"anyOf": [
8+
{ "$ref": "##/components/schemas/MusicMediaBody" },
9+
{ "$ref": "##/components/schemas/BookMediaBody" }
10+
]
11+
},
12+
"examples": {
13+
"MusicMediaBody": { "$ref": "../_examples/musicMediaBody.json" },
14+
"BookMediaBody": { "$ref": "../_examples/bookMediaBody.json" }
15+
}
16+
}
17+
}
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"summary": "BookMediaBody",
3+
"description": "An example book media",
4+
"value": {
5+
"title": "Book Title",
6+
"type": "book",
7+
"isbn": "12345678"
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"summary": "MusicMediaBody",
3+
"description": "An example music media",
4+
"value": {
5+
"title": "Music Title",
6+
"type": "music",
7+
"artist": "Music Artist"
8+
}
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$extend": [
3+
{
4+
"$ref": "../../../_schemas/response.json"
5+
},
6+
{
7+
"properties": {
8+
"data": {
9+
"oneOf": [
10+
{ "$ref": "##/components/schemas/BookMedia" },
11+
{ "$ref": "##/components/schemas/MusicMedia" }
12+
],
13+
"discriminator": {
14+
"propertyName": "type"
15+
}
16+
}
17+
}
18+
}
19+
20+
]
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"allOf": [
3+
{ "$ref": "##/components/schemas/Media" },
4+
{
5+
"type": "object",
6+
"properties": {
7+
"isbn": {
8+
"type": "string"
9+
}
10+
}
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)