1- import { parse as dereference } from "jsonref" ;
21import inflection from "inflection" ;
2+ import type { ParseOptions } from "jsonref" ;
3+ import { parse } from "jsonref" ;
4+ import type { OpenAPIV3 } from "openapi-types" ;
35import { Field } from "../Field.js" ;
6+ import type { OperationType } from "../Operation.js" ;
47import { Operation } from "../Operation.js" ;
58import { Parameter } from "../Parameter.js" ;
69import { Resource } from "../Resource.js" ;
710import getResourcePaths from "../utils/getResources.js" ;
11+ import type {
12+ OpenAPIV3Document ,
13+ OperationObject ,
14+ SchemaObject ,
15+ } from "./dereferencedOpenApiv3.js" ;
816import getType from "./getType.js" ;
9- import type { OpenAPIV3 } from "openapi-types" ;
10- import type { OperationType } from "../Operation.js" ;
11-
12- function isParameter (
13- maybeParameter : NonNullable < OpenAPIV3 . OperationObject [ "parameters" ] > [ number ] ,
14- ) {
15- return maybeParameter !== undefined && "in" in maybeParameter ;
16- }
17-
18- function isSchema (
19- maybeSchema : OpenAPIV3 . MediaTypeObject [ "schema" ] ,
20- ) : maybeSchema is OpenAPIV3 . SchemaObject {
21- // Type predicate can't be inferred because all properties of SchemaObject
22- // type are optional, so we need to check for absence of $ref, but negated
23- // `in` checks can't infer the type.
24- return maybeSchema !== undefined && ! ( "$ref" in maybeSchema ) ;
25- }
26-
27- function isResponse (
28- maybeResponse : OpenAPIV3 . ResponsesObject [ string ] | undefined ,
29- ) {
30- return maybeResponse !== undefined && "description" in maybeResponse ;
31- }
32-
33- function isRequestBody (
34- maybeRequestBody : OpenAPIV3 . OperationObject [ "requestBody" ] ,
35- ) {
36- return maybeRequestBody !== undefined && "content" in maybeRequestBody ;
37- }
38-
39- function getSchemaFromEditOperation (
40- editOperation : OpenAPIV3 . OperationObject | undefined ,
41- ) {
42- if (
43- isRequestBody ( editOperation ?. requestBody ) &&
44- isSchema ( editOperation . requestBody . content ?. [ "application/json" ] ?. schema )
45- ) {
46- return editOperation . requestBody . content [ "application/json" ] . schema ;
47- }
48-
49- return null ;
50- }
51-
52- function getSchemaFromShowOperation (
53- showOperation : OpenAPIV3 . OperationObject | undefined ,
54- document : OpenAPIV3 . Document ,
55- title : string ,
56- ) {
57- if (
58- isResponse ( showOperation ?. responses ?. [ "200" ] ) &&
59- isSchema (
60- showOperation . responses [ "200" ] ?. content ?. [ "application/json" ] ?. schema ,
61- )
62- ) {
63- return showOperation . responses [ "200" ] . content [ "application/json" ] . schema ;
64- }
65-
66- if ( isSchema ( document . components ?. schemas ?. [ title ] ) ) {
67- return document . components . schemas [ title ] ;
68- }
69-
70- return null ;
71- }
7217
7318export function removeTrailingSlash ( url : string ) : string {
7419 if ( url . endsWith ( "/" ) ) {
75- url = url . slice ( 0 , - 1 ) ;
20+ return url . slice ( 0 , - 1 ) ;
7621 }
7722 return url ;
7823}
@@ -101,45 +46,47 @@ function mergeResources(resourceA: Resource, resourceB: Resource) {
10146 return resourceA ;
10247}
10348
49+ function buildEnumObject ( enumArray : SchemaObject [ "enum" ] ) {
50+ if ( ! enumArray ) {
51+ return null ;
52+ }
53+ return Object . fromEntries (
54+ // Object.values is used because the array is annotated: it contains the __meta symbol used by jsonref.
55+ Object . values ( enumArray ) . map ( ( enumValue ) => [
56+ typeof enumValue === "string"
57+ ? inflection . humanize ( enumValue )
58+ : enumValue ,
59+ enumValue ,
60+ ] ) ,
61+ ) ;
62+ }
63+
64+ function getArrayType ( property : SchemaObject ) {
65+ if ( property . type !== "array" ) {
66+ return null ;
67+ }
68+ return getType ( property . items . type || "string" , property . items . format ) ;
69+ }
70+
10471function buildResourceFromSchema (
105- schema : OpenAPIV3 . SchemaObject ,
72+ schema : SchemaObject ,
10673 name : string ,
10774 title : string ,
10875 url : string ,
10976) {
11077 const { description = "" , properties = { } } = schema ;
111- const fieldNames = Object . keys ( properties ) ;
11278 const requiredFields = schema . required || [ ] ;
113-
79+ const fields : Field [ ] = [ ] ;
11480 const readableFields : Field [ ] = [ ] ;
11581 const writableFields : Field [ ] = [ ] ;
11682
117- const fields = fieldNames . map ( ( fieldName ) => {
118- const property = properties [ fieldName ] as OpenAPIV3 . SchemaObject ;
119-
120- const type = getType ( property . type || "string" , property . format ) ;
83+ for ( const [ fieldName , property ] of Object . entries ( properties ) ) {
12184 const field = new Field ( fieldName , {
12285 id : null ,
12386 range : null ,
124- type,
125- arrayType :
126- type === "array" && "items" in property
127- ? getType (
128- ( property . items as OpenAPIV3 . SchemaObject ) . type || "string" ,
129- ( property . items as OpenAPIV3 . SchemaObject ) . format ,
130- )
131- : null ,
132- enum : property . enum
133- ? Object . fromEntries (
134- // Object.values is used because the array is annotated: it contains the __meta symbol used by jsonref.
135- Object . values < string | number > ( property . enum ) . map ( ( enumValue ) => [
136- typeof enumValue === "string"
137- ? inflection . humanize ( enumValue )
138- : enumValue ,
139- enumValue ,
140- ] ) ,
141- )
142- : null ,
87+ type : getType ( property . type || "string" , property . format ) ,
88+ arrayType : getArrayType ( property ) ,
89+ enum : buildEnumObject ( property . enum ) ,
14390 reference : null ,
14491 embedded : null ,
14592 nullable : property . nullable || false ,
@@ -153,9 +100,8 @@ function buildResourceFromSchema(
153100 if ( ! property . readOnly ) {
154101 writableFields . push ( field ) ;
155102 }
156-
157- return field ;
158- } ) ;
103+ fields . push ( field ) ;
104+ }
159105
160106 return new Resource ( name , url , {
161107 id : null ,
@@ -173,14 +119,21 @@ function buildResourceFromSchema(
173119function buildOperationFromPathItem (
174120 httpMethod : `${OpenAPIV3 . HttpMethods } `,
175121 operationType : OperationType ,
176- pathItem : OpenAPIV3 . OperationObject ,
122+ pathItem : OperationObject ,
177123) : Operation {
178124 return new Operation ( pathItem . summary || operationType , operationType , {
179125 method : httpMethod . toUpperCase ( ) ,
180126 deprecated : ! ! pathItem . deprecated ,
181127 } ) ;
182128}
183129
130+ function dereferenceOpenAPIV3 (
131+ response : OpenAPIV3 . Document ,
132+ options : ParseOptions ,
133+ ) : Promise < OpenAPIV3Document > {
134+ return parse ( response , options ) ;
135+ }
136+
184137/*
185138 Assumptions:
186139 RESTful APIs typically have two paths per resources: a `/noun` path and a
@@ -195,9 +148,9 @@ export default async function handleJson(
195148 response : OpenAPIV3 . Document ,
196149 entrypointUrl : string ,
197150) : Promise < Resource [ ] > {
198- const document = ( await dereference ( response , {
151+ const document = await dereferenceOpenAPIV3 ( response , {
199152 scope : entrypointUrl ,
200- } ) ) as OpenAPIV3 . Document ;
153+ } ) ;
201154
202155 const paths = getResourcePaths ( document . paths ) ;
203156
@@ -227,14 +180,12 @@ export default async function handleJson(
227180 if ( ! showOperation && ! editOperation ) {
228181 continue ;
229182 }
183+ const showSchema =
184+ showOperation ?. responses ?. [ "200" ] ?. content ?. [ "application/json" ]
185+ ?. schema || document . components ?. schemas ?. [ title ] ;
230186
231- const showSchema = getSchemaFromShowOperation (
232- showOperation ,
233- document ,
234- title ,
235- ) ;
236-
237- const editSchema = getSchemaFromEditOperation ( editOperation ) ;
187+ const editSchema =
188+ editOperation ?. requestBody ?. content ?. [ "application/json" ] ?. schema || null ;
238189
239190 if ( ! showSchema && ! editSchema ) {
240191 continue ;
@@ -282,22 +233,16 @@ export default async function handleJson(
282233 ] ;
283234
284235 if ( listOperation && listOperation . parameters ) {
285- resource . parameters = listOperation . parameters
286- . filter ( isParameter )
287- . map (
288- ( parameter ) =>
289- new Parameter (
290- parameter . name ,
291- parameter . schema &&
292- isSchema ( parameter . schema ) &&
293- parameter . schema . type
294- ? getType ( parameter . schema . type )
295- : null ,
296- parameter . required || false ,
297- parameter . description || "" ,
298- parameter . deprecated ,
299- ) ,
300- ) ;
236+ resource . parameters = listOperation . parameters . map (
237+ ( parameter ) =>
238+ new Parameter (
239+ parameter . name ,
240+ parameter . schema ?. type ? getType ( parameter . schema . type ) : null ,
241+ parameter . required || false ,
242+ parameter . description || "" ,
243+ parameter . deprecated ,
244+ ) ,
245+ ) ;
301246 }
302247
303248 resources . push ( resource ) ;
0 commit comments