11import type { OpenAPIV3 } from '@gitbook/openapi-parser' ;
2+ import {
3+ OpenAPIMediaTypeExamplesBody ,
4+ OpenAPIMediaTypeExamplesSelector ,
5+ } from './OpenAPICodeSampleInteractive' ;
26import { OpenAPITabs , OpenAPITabsList , OpenAPITabsPanels } from './OpenAPITabs' ;
37import { ScalarApiButton } from './ScalarApiButton' ;
48import { 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' ;
711import { stringifyOpenAPI } from './stringifyOpenAPI' ;
812import type { OpenAPIContextProps , OpenAPIOperationData } from './types' ;
913import { getDefaultServerURL } from './util/server' ;
1014import { checkIsReference , createStateKey } from './utils' ;
1115
16+ const CUSTOM_CODE_SAMPLES_KEYS = [ 'x-custom-examples' , 'x-code-samples' , 'x-codeSamples' ] as const ;
17+
1218/**
1319 * Display code samples to execute the operation.
1420 * It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
1521 */
1622export function OpenAPICodeSample ( props : {
1723 data : OpenAPIOperationData ;
1824 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 ;
1962} ) {
2063 const { data, context } = props ;
2164
@@ -51,97 +94,102 @@ export function OpenAPICodeSample(props: {
5194 const requestBody = ! checkIsReference ( data . operation . requestBody )
5295 ? data . operation . requestBody
5396 : 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 ,
75106 } ;
76107
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 ,
110125 } ) ,
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+ } ;
113140 }
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+ ) ;
121142
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+ }
125171
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+ } > ;
133179}
134180
135181function OpenAPICodeSampleFooter ( props : {
136182 data : OpenAPIOperationData ;
183+ renderers : MediaTypeRenderer [ ] ;
137184 context : OpenAPIContextProps ;
138185} ) {
139- const { data, context } = props ;
186+ const { data, context, renderers } = props ;
140187 const { method, path } = data ;
141188 const { specUrl } = context ;
142189 const hideTryItPanel = data [ 'x-hideTryItPanel' ] || data . operation [ 'x-hideTryItPanel' ] ;
190+ const hasMediaTypes = renderers . length > 0 ;
143191
144- if ( hideTryItPanel ) {
192+ if ( hideTryItPanel && ! hasMediaTypes ) {
145193 return null ;
146194 }
147195
@@ -151,11 +199,59 @@ function OpenAPICodeSampleFooter(props: {
151199
152200 return (
153201 < 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 } /> }
155208 </ div >
156209 ) ;
157210}
158211
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+
159255function getSecurityHeaders ( securities : OpenAPIOperationData [ 'securities' ] ) : {
160256 [ key : string ] : string ;
161257} {
0 commit comments