1- import { OpenAPIV3 } from 'openapi-types' ;
21import { toJSON , fromJSON } from 'flatted' ;
2+ import { OpenAPIV3 } from 'openapi-types' ;
3+
4+ import { resolveOpenAPIPath } from './resolveOpenAPIPath' ;
5+ import { OpenAPIFetcher } from './types' ;
36
47export interface OpenAPIOperationData {
58 path : string ;
@@ -17,25 +20,6 @@ export interface OpenAPIOperationData {
1720
1821export { toJSON , fromJSON } ;
1922
20- export interface OpenAPIFetcher {
21- /**
22- * Fetch an OpenAPI file by its URL.
23- * It should the parsed JSON object or throw an error if the file is not found or can't be parsed.
24- *
25- * It should return a V3 spec.
26- * The data will be mutated.
27- */
28- fetch : ( url : string ) => Promise < any > ;
29-
30- /**
31- * Parse markdown to the react element to render.
32- */
33- parseMarkdown ?: ( input : string ) => Promise < string > ;
34- }
35-
36- export const SYMBOL_REF_RESOLVED = '__$refResolved' ;
37- export const SYMBOL_MARKDOWN_PARSED = '__$markdownParsed' ;
38-
3923/**
4024 * Resolve an OpenAPI operation in a file and compile it to a more usable format.
4125 */
@@ -49,7 +33,7 @@ export async function fetchOpenAPIOperation<Markdown>(
4933) : Promise < OpenAPIOperationData | null > {
5034 const fetcher = cacheFetcher ( rawFetcher ) ;
5135
52- let operation = await resolveOpenAPI < OpenAPIV3 . OperationObject > (
36+ let operation = await resolveOpenAPIPath < OpenAPIV3 . OperationObject > (
5337 input . url ,
5438 [ 'paths' , input . path , input . method ] ,
5539 fetcher ,
@@ -60,7 +44,7 @@ export async function fetchOpenAPIOperation<Markdown>(
6044 }
6145
6246 // Resolve common parameters
63- const commonParameters = await resolveOpenAPI < OpenAPIV3 . ParameterObject [ ] > (
47+ const commonParameters = await resolveOpenAPIPath < OpenAPIV3 . ParameterObject [ ] > (
6448 input . url ,
6549 [ 'paths' , input . path , 'parameters' ] ,
6650 fetcher ,
@@ -73,14 +57,18 @@ export async function fetchOpenAPIOperation<Markdown>(
7357 }
7458
7559 // Resolve servers
76- const servers = await resolveOpenAPI < OpenAPIV3 . ServerObject [ ] > ( input . url , [ 'servers' ] , fetcher ) ;
60+ const servers = await resolveOpenAPIPath < OpenAPIV3 . ServerObject [ ] > (
61+ input . url ,
62+ [ 'servers' ] ,
63+ fetcher ,
64+ ) ;
7765
7866 // Resolve securities
7967 const securities : OpenAPIOperationData [ 'securities' ] = [ ] ;
8068 for ( const security of operation . security ?? [ ] ) {
8169 const securityKey = Object . keys ( security ) [ 0 ] ;
8270
83- const securityScheme = await resolveOpenAPI < OpenAPIV3 . SecuritySchemeObject > (
71+ const securityScheme = await resolveOpenAPIPath < OpenAPIV3 . SecuritySchemeObject > (
8472 input . url ,
8573 [ 'components' , 'securitySchemes' , securityKey ] ,
8674 fetcher ,
@@ -100,145 +88,6 @@ export async function fetchOpenAPIOperation<Markdown>(
10088 } ;
10189}
10290
103- /**
104- * Resolve a path in a OpenAPI file.
105- * It resolves any reference needed to resolve the path, ignoring other references outside the path.
106- */
107- async function resolveOpenAPI < T > (
108- url : string ,
109- dataPath : string [ ] ,
110- fetcher : OpenAPIFetcher ,
111- ) : Promise < T | undefined > {
112- const data = await fetcher . fetch ( url ) ;
113- if ( ! data ) {
114- return undefined ;
115- }
116-
117- let value : unknown = data ;
118-
119- const lastKey = dataPath [ dataPath . length - 1 ] ;
120- dataPath = dataPath . slice ( 0 , - 1 ) ;
121-
122- for ( const part of dataPath ) {
123- if ( typeof value !== 'object' || value === null ) {
124- return undefined ;
125- }
126-
127- // @ts -ignore
128- if ( isRef ( value [ part ] ) ) {
129- await transformAll ( url , value , part , fetcher ) ;
130- }
131-
132- // @ts -ignore
133- value = value [ part ] ;
134- }
135-
136- await transformAll ( url , value , lastKey , fetcher ) ;
137- // @ts -ignore
138- return value [ lastKey ] as T ;
139- }
140-
141- /**
142- * Recursively process a part of the OpenAPI spec to resolve all references.
143- */
144- async function transformAll (
145- url : string ,
146- data : any ,
147- key : string | number ,
148- fetcher : OpenAPIFetcher ,
149- ) : Promise < void > {
150- const value = data [ key ] ;
151-
152- if (
153- typeof value === 'string' &&
154- key === 'description' &&
155- fetcher . parseMarkdown &&
156- ! data [ SYMBOL_MARKDOWN_PARSED ]
157- ) {
158- // Parse markdown
159- data [ SYMBOL_MARKDOWN_PARSED ] = true ;
160- data [ key ] = await fetcher . parseMarkdown ( value ) ;
161- } else if (
162- typeof value === 'string' ||
163- typeof value === 'number' ||
164- typeof value === 'boolean' ||
165- value === null
166- ) {
167- // Primitives
168- } else if ( typeof value === 'object' && value !== null && SYMBOL_REF_RESOLVED in value ) {
169- // Ref was already resolved
170- } else if ( isRef ( value ) ) {
171- const ref = value . $ref ;
172-
173- // Delete the ref to avoid infinite loop with circular references
174- // @ts -ignore
175- delete value . $ref ;
176-
177- data [ key ] = await resolveReference ( url , ref , fetcher ) ;
178- if ( data [ key ] ) {
179- data [ key ] [ SYMBOL_REF_RESOLVED ] = extractRefName ( ref ) ;
180- }
181- } else if ( Array . isArray ( value ) ) {
182- // Recursively resolve all references in the array
183- await Promise . all ( value . map ( ( item , index ) => transformAll ( url , value , index , fetcher ) ) ) ;
184- } else if ( typeof value === 'object' && value !== null ) {
185- // Recursively resolve all references in the object
186- const keys = Object . keys ( value ) ;
187- for ( const key of keys ) {
188- await transformAll ( url , value , key , fetcher ) ;
189- }
190- }
191- }
192-
193- async function resolveReference (
194- origin : string ,
195- ref : string ,
196- fetcher : OpenAPIFetcher ,
197- ) : Promise < any > {
198- const parsed = parseReference ( origin , ref ) ;
199- return resolveOpenAPI ( parsed . url , parsed . dataPath , fetcher ) ;
200- }
201-
202- function parseReference ( origin : string , ref : string ) : { url : string ; dataPath : string [ ] } {
203- if ( ! ref ) {
204- return {
205- url : origin ,
206- dataPath : [ ] ,
207- } ;
208- }
209-
210- if ( ref . startsWith ( '#' ) ) {
211- // Local references
212- const dataPath = ref . split ( '/' ) . filter ( Boolean ) . slice ( 1 ) ;
213- return {
214- url : origin ,
215- dataPath,
216- } ;
217- }
218-
219- // Absolute references
220- const url = new URL ( ref , origin ) ;
221- if ( url . hash ) {
222- const hash = url . hash ;
223- url . hash = '' ;
224- return parseReference ( url . toString ( ) , hash ) ;
225- }
226-
227- return {
228- url : url . toString ( ) ,
229- dataPath : [ ] ,
230- } ;
231- }
232-
233- function extractRefName ( ref : string ) : string {
234- const parts = ref . split ( '/' ) ;
235- return parts [ parts . length - 1 ] ;
236- }
237-
238- function isRef ( ref : any ) : ref is { $ref : string } {
239- return typeof ref === 'object' && ref !== null && '$ref' in ref && ref . $ref ;
240- }
241-
24291function cacheFetcher ( fetcher : OpenAPIFetcher ) : OpenAPIFetcher {
24392 const cache = new Map < string , Promise < any > > ( ) ;
24493
0 commit comments