@@ -2,9 +2,9 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
22import { generateSchemaExample } from './generateSchemaExample' ;
33import type { OpenAPIContextProps , OpenAPIOperationData } from './types' ;
44import { checkIsReference , createStateKey , resolveDescription } from './utils' ;
5- import { stringifyOpenAPI } from './stringifyOpenAPI' ;
65import { OpenAPITabs , OpenAPITabsList , OpenAPITabsPanels } from './OpenAPITabs' ;
76import { InteractiveSection } from './InteractiveSection' ;
7+ import { json2xml } from './json2xml' ;
88
99/**
1010 * Display an example of the response content.
@@ -38,91 +38,264 @@ export function OpenAPIResponseExample(props: {
3838 return Number ( a ) - Number ( b ) ;
3939 } ) ;
4040
41- const examples = responses
42- . map ( ( [ key , value ] ) => {
43- const responseObject = value ;
44- const mediaTypeObject = ( ( ) => {
45- if ( ! responseObject . content ) {
46- return null ;
47- }
48- const key = Object . keys ( responseObject . content ) [ 0 ] ;
49- return (
50- responseObject . content [ 'application/json' ] ??
51- ( key ? responseObject . content [ key ] : null )
52- ) ;
53- } ) ( ) ;
54-
55- if ( ! mediaTypeObject ) {
41+ const tabs = responses
42+ . map ( ( [ key , responseObject ] ) => {
43+ const description = resolveDescription ( responseObject ) ;
44+
45+ if ( checkIsReference ( responseObject ) ) {
5646 return {
5747 key : key ,
5848 label : key ,
59- description : resolveDescription ( responseObject ) ,
60- body : < OpenAPIEmptyResponseExample /> ,
49+ description,
50+ body : (
51+ < OpenAPIExample
52+ example = { getExampleFromReference ( responseObject ) }
53+ context = { context }
54+ syntax = "json"
55+ />
56+ ) ,
6157 } ;
6258 }
6359
64- const example = handleUnresolvedReference (
65- ( ( ) => {
66- const { examples, example } = mediaTypeObject ;
67- if ( examples ) {
68- const key = Object . keys ( examples ) [ 0 ] ;
69- if ( key ) {
70- // @TODO handle multiple examples
71- const firstExample = examples [ key ] ;
72- if ( firstExample ) {
73- return firstExample ;
74- }
75- }
76- }
77-
78- if ( example ) {
79- return { value : example } ;
80- }
81-
82- const schema = mediaTypeObject . schema ;
83- if ( ! schema ) {
84- return null ;
85- }
86-
87- return { value : generateSchemaExample ( schema ) } ;
88- } ) ( ) ,
89- ) ;
60+ if ( ! responseObject . content || Object . keys ( responseObject . content ) . length === 0 ) {
61+ return {
62+ key : key ,
63+ label : key ,
64+ description,
65+ body : < OpenAPIEmptyResponseExample /> ,
66+ } ;
67+ }
9068
9169 return {
9270 key : key ,
9371 label : key ,
9472 description : resolveDescription ( responseObject ) ,
95- body : example ?. value ? (
96- < context . CodeBlock
97- code = {
98- typeof example . value === 'string'
99- ? example . value
100- : stringifyOpenAPI ( example . value , null , 2 )
101- }
102- syntax = "json"
103- />
104- ) : (
105- < OpenAPIEmptyResponseExample />
106- ) ,
73+ body : < OpenAPIResponse context = { context } content = { responseObject . content } /> ,
10774 } ;
10875 } )
10976 . filter ( ( val ) : val is { key : string ; label : string ; body : any ; description : string } =>
11077 Boolean ( val ) ,
11178 ) ;
11279
113- if ( examples . length === 0 ) {
80+ if ( tabs . length === 0 ) {
11481 return null ;
11582 }
11683
11784 return (
118- < OpenAPITabs stateKey = { createStateKey ( 'response-example' ) } items = { examples } >
85+ < OpenAPITabs stateKey = { createStateKey ( 'response-example' ) } items = { tabs } >
11986 < InteractiveSection header = { < OpenAPITabsList /> } className = "openapi-response-example" >
12087 < OpenAPITabsPanels />
12188 </ InteractiveSection >
12289 </ OpenAPITabs >
12390 ) ;
12491}
12592
93+ function OpenAPIResponse ( props : {
94+ context : OpenAPIContextProps ;
95+ content : {
96+ [ media : string ] : OpenAPIV3 . MediaTypeObject ;
97+ } ;
98+ } ) {
99+ const { context, content } = props ;
100+
101+ const entries = Object . entries ( content ) ;
102+ const firstEntry = entries [ 0 ] ;
103+
104+ if ( ! firstEntry ) {
105+ throw new Error ( 'One media type is required' ) ;
106+ }
107+
108+ if ( entries . length === 1 ) {
109+ const [ mediaType , mediaTypeObject ] = firstEntry ;
110+ return (
111+ < OpenAPIResponseMediaType
112+ context = { context }
113+ mediaType = { mediaType }
114+ mediaTypeObject = { mediaTypeObject }
115+ />
116+ ) ;
117+ }
118+
119+ const tabs = entries . map ( ( entry ) => {
120+ const [ mediaType , mediaTypeObject ] = entry ;
121+ return {
122+ key : mediaType ,
123+ label : mediaType ,
124+ body : (
125+ < OpenAPIResponseMediaType
126+ context = { context }
127+ mediaType = { mediaType }
128+ mediaTypeObject = { mediaTypeObject }
129+ />
130+ ) ,
131+ } ;
132+ } ) ;
133+
134+ return (
135+ < OpenAPITabs stateKey = { createStateKey ( 'response-media-types' ) } items = { tabs } >
136+ < InteractiveSection
137+ header = { < OpenAPITabsList /> }
138+ className = "openapi-response-media-types"
139+ >
140+ < OpenAPITabsPanels />
141+ </ InteractiveSection >
142+ </ OpenAPITabs >
143+ ) ;
144+ }
145+
146+ function OpenAPIResponseMediaType ( props : {
147+ mediaTypeObject : OpenAPIV3 . MediaTypeObject ;
148+ mediaType : string ;
149+ context : OpenAPIContextProps ;
150+ } ) {
151+ const { mediaTypeObject, mediaType } = props ;
152+ const examples = getExamplesFromMediaTypeObject ( { mediaTypeObject, mediaType } ) ;
153+ const syntax = getSyntaxFromMediaType ( mediaType ) ;
154+ const firstExample = examples [ 0 ] ;
155+
156+ if ( ! firstExample ) {
157+ return < OpenAPIEmptyResponseExample /> ;
158+ }
159+
160+ if ( examples . length === 1 ) {
161+ return (
162+ < OpenAPIExample
163+ example = { firstExample . example }
164+ context = { props . context }
165+ syntax = { syntax }
166+ />
167+ ) ;
168+ }
169+
170+ const tabs = examples . map ( ( example ) => {
171+ return {
172+ key : example . key ,
173+ label : example . example . summary || example . key ,
174+ body : (
175+ < OpenAPIExample
176+ example = { firstExample . example }
177+ context = { props . context }
178+ syntax = { syntax }
179+ />
180+ ) ,
181+ } ;
182+ } ) ;
183+
184+ return (
185+ < OpenAPITabs stateKey = { createStateKey ( 'response-media-type-examples' ) } items = { tabs } >
186+ < InteractiveSection
187+ header = { < OpenAPITabsList /> }
188+ className = "openapi-response-media-type-examples"
189+ >
190+ < OpenAPITabsPanels />
191+ </ InteractiveSection >
192+ </ OpenAPITabs >
193+ ) ;
194+ }
195+
196+ /**
197+ * Display an example.
198+ */
199+ function OpenAPIExample ( props : {
200+ example : OpenAPIV3 . ExampleObject ;
201+ context : OpenAPIContextProps ;
202+ syntax : string ;
203+ } ) {
204+ const { example, context, syntax } = props ;
205+ const code = stringifyExample ( { example, xml : syntax === 'xml' } ) ;
206+
207+ if ( code === null ) {
208+ return < OpenAPIEmptyResponseExample /> ;
209+ }
210+
211+ return < context . CodeBlock code = { code } syntax = { syntax } /> ;
212+ }
213+
214+ function stringifyExample ( args : { example : OpenAPIV3 . ExampleObject ; xml : boolean } ) : string | null {
215+ const { example, xml } = args ;
216+
217+ if ( ! example . value ) {
218+ return null ;
219+ }
220+
221+ if ( typeof example . value === 'string' ) {
222+ return example . value ;
223+ }
224+
225+ if ( xml ) {
226+ return json2xml ( example . value ) ;
227+ }
228+
229+ return JSON . stringify ( example . value , null , 2 ) ;
230+ }
231+
232+ /**
233+ * Get the syntax from a media type.
234+ */
235+ function getSyntaxFromMediaType ( mediaType : string ) : string {
236+ if ( mediaType . includes ( 'json' ) ) {
237+ return 'json' ;
238+ }
239+
240+ if ( mediaType === 'application/xml' ) {
241+ return 'xml' ;
242+ }
243+
244+ return 'text' ;
245+ }
246+
247+ /**
248+ * Get examples from a media type object.
249+ */
250+ function getExamplesFromMediaTypeObject ( args : {
251+ mediaType : string ;
252+ mediaTypeObject : OpenAPIV3 . MediaTypeObject ;
253+ } ) : { key : string ; example : OpenAPIV3 . ExampleObject } [ ] {
254+ const { mediaTypeObject, mediaType } = args ;
255+ if ( mediaTypeObject . examples ) {
256+ return Object . entries ( mediaTypeObject . examples ) . map ( ( [ key , example ] ) => {
257+ return {
258+ key,
259+ example : checkIsReference ( example ) ? getExampleFromReference ( example ) : example ,
260+ } ;
261+ } ) ;
262+ }
263+
264+ if ( mediaTypeObject . example ) {
265+ return [ { key : 'default' , example : { value : mediaTypeObject . example } } ] ;
266+ }
267+
268+ if ( mediaTypeObject . schema ) {
269+ if ( mediaType === 'application/xml' ) {
270+ // @TODO normally we should use the name of the schema but we don't have it
271+ // fix it when we got the reference name
272+ const root = mediaTypeObject . schema . xml ?. name ?? 'object' ;
273+ return [
274+ {
275+ key : 'default' ,
276+ example : {
277+ value : {
278+ [ root ] : generateSchemaExample ( mediaTypeObject . schema , {
279+ xml : mediaType === 'application/xml' ,
280+ } ) ,
281+ } ,
282+ } ,
283+ } ,
284+ ] ;
285+ }
286+ return [
287+ {
288+ key : 'default' ,
289+ example : { value : generateSchemaExample ( mediaTypeObject . schema ) } ,
290+ } ,
291+ ] ;
292+ }
293+ return [ ] ;
294+ }
295+
296+ /**
297+ * Empty response example.
298+ */
126299function OpenAPIEmptyResponseExample ( ) {
127300 return (
128301 < pre className = "openapi-response-example-empty" >
@@ -131,15 +304,9 @@ function OpenAPIEmptyResponseExample() {
131304 ) ;
132305}
133306
134- function handleUnresolvedReference (
135- input : OpenAPIV3 . ExampleObject | null ,
136- ) : OpenAPIV3 . ExampleObject | null {
137- const isReference = checkIsReference ( input ?. value ) ;
138-
139- if ( isReference ) {
140- // If we find a reference that wasn't resolved or needed to be resolved externally, render out the URL
141- return { value : input . value . $ref } ;
142- }
143-
144- return input ;
307+ /**
308+ * Generate an example from a reference object.
309+ */
310+ function getExampleFromReference ( ref : OpenAPIV3 . ReferenceObject ) : OpenAPIV3 . ExampleObject {
311+ return { summary : 'Unresolved reference' , value : { $ref : ref . $ref } } ;
145312}
0 commit comments