1
1
import type { OpenAPIV3 } from '@gitbook/openapi-parser' ;
2
+ import {
3
+ OpenAPIMediaTypeExamplesBody ,
4
+ OpenAPIMediaTypeExamplesSelector ,
5
+ } from './OpenAPICodeSampleInteractive' ;
2
6
import { OpenAPITabs , OpenAPITabsList , OpenAPITabsPanels } from './OpenAPITabs' ;
3
7
import { ScalarApiButton } from './ScalarApiButton' ;
4
8
import { StaticSection } from './StaticSection' ;
5
- import { type CodeSampleInput , codeSampleGenerators } from './code-samples' ;
6
- import { generateMediaTypeExample , generateSchemaExample } from './generateSchemaExample' ;
9
+ import { type CodeSampleGenerator , codeSampleGenerators } from './code-samples' ;
10
+ import { generateMediaTypeExamples , generateSchemaExample } from './generateSchemaExample' ;
7
11
import { stringifyOpenAPI } from './stringifyOpenAPI' ;
8
12
import type { OpenAPIContextProps , OpenAPIOperationData } from './types' ;
9
13
import { getDefaultServerURL } from './util/server' ;
10
14
import { checkIsReference , createStateKey } from './utils' ;
11
15
16
+ const CUSTOM_CODE_SAMPLES_KEYS = [ 'x-custom-examples' , 'x-code-samples' , 'x-codeSamples' ] as const ;
17
+
12
18
/**
13
19
* Display code samples to execute the operation.
14
20
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
15
21
*/
16
22
export function OpenAPICodeSample ( props : {
17
23
data : OpenAPIOperationData ;
18
24
context : OpenAPIContextProps ;
25
+ } ) {
26
+ const { data } = props ;
27
+
28
+ // If code samples are disabled at operation level, we don't display the code samples.
29
+ if ( data . operation [ 'x-codeSamples' ] === false ) {
30
+ return null ;
31
+ }
32
+
33
+ const customCodeSamples = getCustomCodeSamples ( props ) ;
34
+
35
+ // If code samples are disabled at the top-level and not custom code samples are defined,
36
+ // we don't display the code samples.
37
+ if ( data [ 'x-codeSamples' ] === false && ! customCodeSamples ) {
38
+ return null ;
39
+ }
40
+
41
+ const samples = customCodeSamples ?? generateCodeSamples ( props ) ;
42
+
43
+ if ( samples . length === 0 ) {
44
+ return null ;
45
+ }
46
+
47
+ return (
48
+ < OpenAPITabs stateKey = { createStateKey ( 'codesample' ) } items = { samples } >
49
+ < StaticSection header = { < OpenAPITabsList /> } className = "openapi-codesample" >
50
+ < OpenAPITabsPanels />
51
+ </ StaticSection >
52
+ </ OpenAPITabs >
53
+ ) ;
54
+ }
55
+
56
+ /**
57
+ * Generate code samples for the operation.
58
+ */
59
+ function generateCodeSamples ( props : {
60
+ data : OpenAPIOperationData ;
61
+ context : OpenAPIContextProps ;
19
62
} ) {
20
63
const { data, context } = props ;
21
64
@@ -51,97 +94,102 @@ export function OpenAPICodeSample(props: {
51
94
const requestBody = ! checkIsReference ( data . operation . requestBody )
52
95
? data . operation . requestBody
53
96
: undefined ;
54
- const requestBodyContentEntries = requestBody ?. content
55
- ? Object . entries ( requestBody . content )
56
- : undefined ;
57
- const requestBodyContent = requestBodyContentEntries ?. [ 0 ] ;
58
-
59
- const input : CodeSampleInput = {
60
- url :
61
- getDefaultServerURL ( data . servers ) +
62
- data . path +
63
- ( searchParams . size ? `?${ searchParams . toString ( ) } ` : '' ) ,
64
- method : data . method ,
65
- body : requestBodyContent ? generateMediaTypeExample ( requestBodyContent [ 1 ] ) : undefined ,
66
- headers : {
67
- ...getSecurityHeaders ( data . securities ) ,
68
- ...headersObject ,
69
- ...( requestBodyContent
70
- ? {
71
- 'Content-Type' : requestBodyContent [ 0 ] ,
72
- }
73
- : undefined ) ,
74
- } ,
97
+
98
+ const url =
99
+ getDefaultServerURL ( data . servers ) +
100
+ data . path +
101
+ ( searchParams . size ? `?${ searchParams . toString ( ) } ` : '' ) ;
102
+
103
+ const genericHeaders = {
104
+ ...getSecurityHeaders ( data . securities ) ,
105
+ ...headersObject ,
75
106
} ;
76
107
77
- const autoCodeSamples = codeSampleGenerators . map ( ( generator ) => ( {
78
- key : `default-${ generator . id } ` ,
79
- label : generator . label ,
80
- body : context . renderCodeBlock ( {
81
- code : generator . generate ( input ) ,
82
- syntax : generator . syntax ,
83
- } ) ,
84
- footer : < OpenAPICodeSampleFooter data = { data } context = { context } /> ,
85
- } ) ) ;
86
-
87
- // Use custom samples if defined
88
- let customCodeSamples : null | Array < {
89
- key : string ;
90
- label : string ;
91
- body : React . ReactNode ;
92
- } > = null ;
93
- ( [ 'x-custom-examples' , 'x-code-samples' , 'x-codeSamples' ] as const ) . forEach ( ( key ) => {
94
- const customSamples = data . operation [ key ] ;
95
- if ( customSamples && Array . isArray ( customSamples ) ) {
96
- customCodeSamples = customSamples
97
- . filter ( ( sample ) => {
98
- return (
99
- typeof sample . label === 'string' &&
100
- typeof sample . source === 'string' &&
101
- typeof sample . lang === 'string'
102
- ) ;
103
- } )
104
- . map ( ( sample , index ) => ( {
105
- key : `redocly-${ sample . lang } -${ index } ` ,
106
- label : sample . label ,
107
- body : context . renderCodeBlock ( {
108
- code : sample . source ,
109
- syntax : sample . lang ,
108
+ const mediaTypeRendererFactories = Object . entries ( requestBody ?. content ?? { } ) . map (
109
+ ( [ mediaType , mediaTypeObject ] ) => {
110
+ return ( generator : CodeSampleGenerator ) => {
111
+ const mediaTypeHeaders = {
112
+ ...genericHeaders ,
113
+ 'Content-Type' : mediaType ,
114
+ } ;
115
+ return {
116
+ mediaType,
117
+ element : context . renderCodeBlock ( {
118
+ code : generator . generate ( {
119
+ url,
120
+ method : data . method ,
121
+ body : undefined ,
122
+ headers : mediaTypeHeaders ,
123
+ } ) ,
124
+ syntax : generator . syntax ,
110
125
} ) ,
111
- footer : < OpenAPICodeSampleFooter data = { data } context = { context } /> ,
112
- } ) ) ;
126
+ examples : generateMediaTypeExamples ( mediaTypeObject ) . map ( ( example ) => ( {
127
+ example,
128
+ element : context . renderCodeBlock ( {
129
+ code : generator . generate ( {
130
+ url,
131
+ method : data . method ,
132
+ body : example . value ,
133
+ headers : mediaTypeHeaders ,
134
+ } ) ,
135
+ syntax : generator . syntax ,
136
+ } ) ,
137
+ } ) ) ,
138
+ } satisfies MediaTypeRenderer ;
139
+ } ;
113
140
}
114
- } ) ;
115
-
116
- // Code samples can be disabled at the top-level or at the operation level
117
- // If code samples are defined at the operation level, it will override the top-level setting
118
- const codeSamplesDisabled =
119
- data [ 'x-codeSamples' ] === false || data . operation [ 'x-codeSamples' ] === false ;
120
- const samples = customCodeSamples ?? ( ! codeSamplesDisabled ? autoCodeSamples : [ ] ) ;
141
+ ) ;
121
142
122
- if ( samples . length === 0 ) {
123
- return null ;
124
- }
143
+ return codeSampleGenerators . map ( ( generator ) => {
144
+ if ( mediaTypeRendererFactories . length > 0 ) {
145
+ const renderers = mediaTypeRendererFactories . map ( ( generate ) => generate ( generator ) ) ;
146
+ return {
147
+ key : `default-${ generator . id } ` ,
148
+ label : generator . label ,
149
+ body : < OpenAPIMediaTypeExamplesBody data = { data } renderers = { renderers } /> ,
150
+ footer : (
151
+ < OpenAPICodeSampleFooter renderers = { renderers } data = { data } context = { context } />
152
+ ) ,
153
+ } ;
154
+ }
155
+ return {
156
+ key : `default-${ generator . id } ` ,
157
+ label : generator . label ,
158
+ body : context . renderCodeBlock ( {
159
+ code : generator . generate ( {
160
+ url,
161
+ method : data . method ,
162
+ body : undefined ,
163
+ headers : genericHeaders ,
164
+ } ) ,
165
+ syntax : generator . syntax ,
166
+ } ) ,
167
+ footer : < OpenAPICodeSampleFooter data = { data } renderers = { [ ] } context = { context } /> ,
168
+ } ;
169
+ } ) ;
170
+ }
125
171
126
- return (
127
- < OpenAPITabs stateKey = { createStateKey ( 'codesample' ) } items = { samples } >
128
- < StaticSection header = { < OpenAPITabsList /> } className = "openapi-codesample" >
129
- < OpenAPITabsPanels />
130
- </ StaticSection >
131
- </ OpenAPITabs >
132
- ) ;
172
+ export interface MediaTypeRenderer {
173
+ mediaType : string ;
174
+ element : React . ReactNode ;
175
+ examples : Array < {
176
+ example : OpenAPIV3 . ExampleObject ;
177
+ element : React . ReactNode ;
178
+ } > ;
133
179
}
134
180
135
181
function OpenAPICodeSampleFooter ( props : {
136
182
data : OpenAPIOperationData ;
183
+ renderers : MediaTypeRenderer [ ] ;
137
184
context : OpenAPIContextProps ;
138
185
} ) {
139
- const { data, context } = props ;
186
+ const { data, context, renderers } = props ;
140
187
const { method, path } = data ;
141
188
const { specUrl } = context ;
142
189
const hideTryItPanel = data [ 'x-hideTryItPanel' ] || data . operation [ 'x-hideTryItPanel' ] ;
190
+ const hasMediaTypes = renderers . length > 0 ;
143
191
144
- if ( hideTryItPanel ) {
192
+ if ( hideTryItPanel && ! hasMediaTypes ) {
145
193
return null ;
146
194
}
147
195
@@ -151,11 +199,59 @@ function OpenAPICodeSampleFooter(props: {
151
199
152
200
return (
153
201
< div className = "openapi-codesample-footer" >
154
- < ScalarApiButton method = { method } path = { path } specUrl = { specUrl } />
202
+ { hasMediaTypes ? (
203
+ < OpenAPIMediaTypeExamplesSelector data = { data } renderers = { renderers } />
204
+ ) : (
205
+ < span />
206
+ ) }
207
+ { ! hideTryItPanel && < ScalarApiButton method = { method } path = { path } specUrl = { specUrl } /> }
155
208
</ div >
156
209
) ;
157
210
}
158
211
212
+ /**
213
+ * Get custom code samples for the operation.
214
+ */
215
+ function getCustomCodeSamples ( props : {
216
+ data : OpenAPIOperationData ;
217
+ context : OpenAPIContextProps ;
218
+ } ) {
219
+ const { data, context } = props ;
220
+
221
+ let customCodeSamples : null | Array < {
222
+ key : string ;
223
+ label : string ;
224
+ body : React . ReactNode ;
225
+ } > = null ;
226
+
227
+ CUSTOM_CODE_SAMPLES_KEYS . forEach ( ( key ) => {
228
+ const customSamples = data . operation [ key ] ;
229
+ if ( customSamples && Array . isArray ( customSamples ) ) {
230
+ customCodeSamples = customSamples
231
+ . filter ( ( sample ) => {
232
+ return (
233
+ typeof sample . label === 'string' &&
234
+ typeof sample . source === 'string' &&
235
+ typeof sample . lang === 'string'
236
+ ) ;
237
+ } )
238
+ . map ( ( sample , index ) => ( {
239
+ key : `custom-sample-${ sample . lang } -${ index } ` ,
240
+ label : sample . label ,
241
+ body : context . renderCodeBlock ( {
242
+ code : sample . source ,
243
+ syntax : sample . lang ,
244
+ } ) ,
245
+ footer : (
246
+ < OpenAPICodeSampleFooter renderers = { [ ] } data = { data } context = { context } />
247
+ ) ,
248
+ } ) ) ;
249
+ }
250
+ } ) ;
251
+
252
+ return customCodeSamples ;
253
+ }
254
+
159
255
function getSecurityHeaders ( securities : OpenAPIOperationData [ 'securities' ] ) : {
160
256
[ key : string ] : string ;
161
257
} {
0 commit comments