@@ -2,9 +2,9 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
import { generateSchemaExample } from './generateSchemaExample' ;
3
3
import type { OpenAPIContextProps , OpenAPIOperationData } from './types' ;
4
4
import { checkIsReference , createStateKey , resolveDescription } from './utils' ;
5
- import { stringifyOpenAPI } from './stringifyOpenAPI' ;
6
5
import { OpenAPITabs , OpenAPITabsList , OpenAPITabsPanels } from './OpenAPITabs' ;
7
6
import { InteractiveSection } from './InteractiveSection' ;
7
+ import { json2xml } from './json2xml' ;
8
8
9
9
/**
10
10
* Display an example of the response content.
@@ -38,91 +38,231 @@ export function OpenAPIResponseExample(props: {
38
38
return Number ( a ) - Number ( b ) ;
39
39
} ) ;
40
40
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 ) ) {
56
46
return {
57
47
key : key ,
58
48
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
+ ) ,
61
57
} ;
62
58
}
63
59
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
+ }
90
68
91
69
return {
92
70
key : key ,
93
71
label : key ,
94
72
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 } /> ,
107
74
} ;
108
75
} )
109
76
. filter ( ( val ) : val is { key : string ; label : string ; body : any ; description : string } =>
110
77
Boolean ( val ) ,
111
78
) ;
112
79
113
- if ( examples . length === 0 ) {
80
+ if ( tabs . length === 0 ) {
114
81
return null ;
115
82
}
116
83
117
84
return (
118
- < OpenAPITabs stateKey = { createStateKey ( 'response-example' ) } items = { examples } >
85
+ < OpenAPITabs stateKey = { createStateKey ( 'response-example' ) } items = { tabs } >
86
+ < InteractiveSection header = { < OpenAPITabsList /> } className = "openapi-response-example" >
87
+ < OpenAPITabsPanels />
88
+ </ InteractiveSection >
89
+ </ OpenAPITabs >
90
+ ) ;
91
+ }
92
+
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 ( 'media-type-examples' ) } items = { tabs } >
136
+ < InteractiveSection header = { < OpenAPITabsList /> } className = "openapi-response-example" >
137
+ < OpenAPITabsPanels />
138
+ </ InteractiveSection >
139
+ </ OpenAPITabs >
140
+ ) ;
141
+ }
142
+
143
+ function OpenAPIResponseMediaType ( props : {
144
+ mediaTypeObject : OpenAPIV3 . MediaTypeObject ;
145
+ mediaType : string ;
146
+ context : OpenAPIContextProps ;
147
+ } ) {
148
+ const { mediaTypeObject, mediaType } = props ;
149
+ const examples = getExamplesFromMediaTypeObject ( { mediaTypeObject, mediaType } ) ;
150
+ const syntax = getSyntaxFromMediaType ( mediaType ) ;
151
+ const firstExample = examples [ 0 ] ;
152
+
153
+ if ( ! firstExample ) {
154
+ return < OpenAPIEmptyResponseExample /> ;
155
+ }
156
+
157
+ if ( examples . length === 1 ) {
158
+ return < OpenAPIExample example = { firstExample } context = { props . context } syntax = { syntax } /> ;
159
+ }
160
+
161
+ const tabs = examples . map ( ( example ) => {
162
+ return {
163
+ key : example . summary || 'Example' ,
164
+ label : example . summary || 'Example' ,
165
+ body : < OpenAPIExample example = { firstExample } context = { props . context } syntax = { syntax } /> ,
166
+ } ;
167
+ } ) ;
168
+
169
+ return (
170
+ < OpenAPITabs stateKey = { createStateKey ( 'media-type-examples' ) } items = { tabs } >
119
171
< InteractiveSection header = { < OpenAPITabsList /> } className = "openapi-response-example" >
120
172
< OpenAPITabsPanels />
121
173
</ InteractiveSection >
122
174
</ OpenAPITabs >
123
175
) ;
124
176
}
125
177
178
+ /**
179
+ * Display an example.
180
+ */
181
+ function OpenAPIExample ( props : {
182
+ example : OpenAPIV3 . ExampleObject ;
183
+ context : OpenAPIContextProps ;
184
+ syntax : string ;
185
+ } ) {
186
+ const { example, context, syntax } = props ;
187
+ const code = stringifyExample ( { example, xml : syntax === 'xml' } ) ;
188
+
189
+ if ( code === null ) {
190
+ return < OpenAPIEmptyResponseExample /> ;
191
+ }
192
+
193
+ return < context . CodeBlock code = { code } syntax = { syntax } /> ;
194
+ }
195
+
196
+ function stringifyExample ( args : { example : OpenAPIV3 . ExampleObject ; xml : boolean } ) : string | null {
197
+ const { example, xml } = args ;
198
+
199
+ if ( ! example . value ) {
200
+ return null ;
201
+ }
202
+
203
+ if ( typeof example . value === 'string' ) {
204
+ return example . value ;
205
+ }
206
+
207
+ if ( xml ) {
208
+ return json2xml ( example . value ) ;
209
+ }
210
+
211
+ return JSON . stringify ( example . value , null , 2 ) ;
212
+ }
213
+
214
+ /**
215
+ * Get the syntax from a media type.
216
+ */
217
+ function getSyntaxFromMediaType ( mediaType : string ) : string {
218
+ if ( mediaType . includes ( 'json' ) ) {
219
+ return 'json' ;
220
+ }
221
+
222
+ if ( mediaType === 'application/xml' ) {
223
+ return 'xml' ;
224
+ }
225
+
226
+ return 'text' ;
227
+ }
228
+
229
+ /**
230
+ * Get examples from a media type object.
231
+ */
232
+ function getExamplesFromMediaTypeObject ( args : {
233
+ mediaType : string ;
234
+ mediaTypeObject : OpenAPIV3 . MediaTypeObject ;
235
+ } ) : OpenAPIV3 . ExampleObject [ ] {
236
+ const { mediaTypeObject, mediaType } = args ;
237
+ if ( mediaTypeObject . examples ) {
238
+ return Object . values ( mediaTypeObject . examples ) . map ( ( example ) => {
239
+ return checkIsReference ( example ) ? getExampleFromReference ( example ) : example ;
240
+ } ) ;
241
+ }
242
+
243
+ if ( mediaTypeObject . example ) {
244
+ return [ { value : mediaTypeObject . example } ] ;
245
+ }
246
+
247
+ if ( mediaTypeObject . schema ) {
248
+ // @TODO normally we should use the name of the schema but we don't have it
249
+ const root = mediaTypeObject . schema . xml ?. name ?? 'object' ;
250
+ return [
251
+ {
252
+ value : {
253
+ [ root ] : generateSchemaExample ( mediaTypeObject . schema , {
254
+ xml : mediaType === 'application/xml' ,
255
+ } ) ,
256
+ } ,
257
+ } ,
258
+ ] ;
259
+ }
260
+ return [ ] ;
261
+ }
262
+
263
+ /**
264
+ * Empty response example.
265
+ */
126
266
function OpenAPIEmptyResponseExample ( ) {
127
267
return (
128
268
< pre className = "openapi-response-example-empty" >
@@ -131,15 +271,9 @@ function OpenAPIEmptyResponseExample() {
131
271
) ;
132
272
}
133
273
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 ;
274
+ /**
275
+ * Generate an example from a reference object.
276
+ */
277
+ function getExampleFromReference ( ref : OpenAPIV3 . ReferenceObject ) : OpenAPIV3 . ExampleObject {
278
+ return { summary : 'Unresolved reference' , value : { $ref : ref . $ref } } ;
145
279
}
0 commit comments