Skip to content

Commit 8093c4b

Browse files
authored
Refactor: Extract shared content type utilities to reduce code duplication (#56803)
1 parent bbd6ab4 commit 8093c4b

File tree

3 files changed

+110
-107
lines changed

3 files changed

+110
-107
lines changed

src/rest/components/RestCodeSamples.tsx

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import cx from 'classnames'
88
import hljs from 'highlight.js/lib/core'
99
import json from 'highlight.js/lib/languages/json'
1010
import javascript from 'highlight.js/lib/languages/javascript'
11+
import { generateExampleOptions } from '@/rest/lib/content-type-utils'
1112
import hljsCurl from 'highlightjs-curl'
1213

1314
import { useTranslation } from '@/languages/components/useTranslation'
@@ -94,51 +95,7 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
9495
}
9596

9697
// Menu options for the example selector
97-
98-
// We show the media type in the examples menu items for each example if
99-
// there's more than one example and if the media types aren't all the same
100-
// for the examples (e.g. if all examples have content type `application/json`,
101-
// we won't show that information in the menu items).
102-
const responseContentTypesDiffer =
103-
languageExamples.length > 1 &&
104-
!languageExamples.every(
105-
(example) => example.response.contentType === languageExamples[0].response.contentType,
106-
)
107-
108-
// Check if request content types differ between examples
109-
const requestContentTypesDiffer =
110-
languageExamples.length > 1 &&
111-
!languageExamples.every(
112-
(example) => example.request?.contentType === languageExamples[0].request?.contentType,
113-
)
114-
115-
const showExampleOptionMediaType = responseContentTypesDiffer || requestContentTypesDiffer
116-
117-
const exampleSelectOptions = languageExamples.map((example, index) => {
118-
const requestContentType = example.request?.contentType
119-
const responseContentType = example.response.contentType
120-
121-
let text = example.description
122-
123-
if (showExampleOptionMediaType) {
124-
if (requestContentTypesDiffer && responseContentTypesDiffer) {
125-
// Show both request and response content types
126-
text = `${example.description} (${requestContentType}${responseContentType})`
127-
} else if (requestContentTypesDiffer) {
128-
// Show only request content type
129-
text = `${example.description} (${requestContentType})`
130-
} else if (responseContentTypesDiffer) {
131-
// Show only response content type
132-
text = `${example.description} (${responseContentType})`
133-
}
134-
}
135-
136-
return {
137-
text,
138-
// maps to the index of the example in the languageExamples array
139-
languageIndex: index,
140-
}
141-
})
98+
const exampleSelectOptions = generateExampleOptions(languageExamples)
14299

143100
const [selectedLanguage, setSelectedLanguage] = useState(languageSelectOptions[0])
144101
const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0])

src/rest/lib/content-type-utils.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Interface for processed code examples as used by the RestCodeSamples component.
3+
* This represents the structure AFTER the component processes the original API data,
4+
* where sample.request.description is moved to the top level as description.
5+
*
6+
* Original API structure: { request: { description: string, contentType: string }, response: {...} }
7+
* Processed structure: { description: string, request: { contentType: string }, response: {...} }
8+
*/
9+
export interface CodeExample {
10+
request?: {
11+
contentType?: string
12+
}
13+
response: {
14+
contentType: string
15+
}
16+
description: string
17+
}
18+
19+
export interface ExampleOption {
20+
text: string
21+
languageIndex: number
22+
}
23+
24+
/**
25+
* Determines if request content types differ between examples
26+
*/
27+
export function shouldShowRequestContentType(examples: CodeExample[]): boolean {
28+
return (
29+
examples.length > 1 &&
30+
!examples.every((example) => example.request?.contentType === examples[0].request?.contentType)
31+
)
32+
}
33+
34+
/**
35+
* Determines if response content types differ between examples
36+
*/
37+
export function shouldShowResponseContentType(examples: CodeExample[]): boolean {
38+
return (
39+
examples.length > 1 &&
40+
!examples.every((example) => example.response.contentType === examples[0].response.contentType)
41+
)
42+
}
43+
44+
/**
45+
* Generates example option objects with appropriate content type labels
46+
* This matches the exact logic from RestCodeSamples.tsx
47+
*/
48+
export function generateExampleOptions(examples: CodeExample[]): ExampleOption[] {
49+
const responseContentTypesDiffer = shouldShowResponseContentType(examples)
50+
const requestContentTypesDiffer = shouldShowRequestContentType(examples)
51+
const showExampleOptionMediaType = responseContentTypesDiffer || requestContentTypesDiffer
52+
53+
return examples.map((example, index) => {
54+
const requestContentType = example.request?.contentType
55+
const responseContentType = example.response.contentType
56+
57+
let text = example.description
58+
59+
if (showExampleOptionMediaType) {
60+
if (requestContentTypesDiffer && responseContentTypesDiffer) {
61+
// Show both request and response content types
62+
text = `${example.description} (${requestContentType}${responseContentType})`
63+
} else if (requestContentTypesDiffer) {
64+
// Show only request content type
65+
text = `${example.description} (${requestContentType})`
66+
} else if (responseContentTypesDiffer) {
67+
// Show only response content type
68+
text = `${example.description} (${responseContentType})`
69+
}
70+
}
71+
72+
return {
73+
text,
74+
languageIndex: index,
75+
}
76+
})
77+
}
78+
79+
/**
80+
* Generates just the text labels for example options (useful for testing)
81+
*/
82+
export function generateExampleOptionTexts(examples: CodeExample[]): string[] {
83+
return generateExampleOptions(examples).map((option) => option.text)
84+
}

src/rest/tests/content-type-logic.js

Lines changed: 24 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,21 @@
11
import { describe, expect, test } from 'vitest'
2+
import {
3+
shouldShowRequestContentType,
4+
shouldShowResponseContentType,
5+
generateExampleOptionTexts,
6+
} from '@/rest/lib/content-type-utils'
27

38
describe('Request Content Type Logic', () => {
4-
// Helper function to extract the logic from RestCodeSamples
5-
function shouldShowRequestContentType(codeExamples) {
6-
const requestContentTypesDiffer =
7-
codeExamples.length > 1 &&
8-
!codeExamples.every(
9-
(example) => example.request?.contentType === codeExamples[0].request?.contentType,
10-
)
11-
return requestContentTypesDiffer
12-
}
13-
14-
function shouldShowResponseContentType(codeExamples) {
15-
const responseContentTypesDiffer =
16-
codeExamples.length > 1 &&
17-
!codeExamples.every(
18-
(example) => example.response?.contentType === codeExamples[0].response?.contentType,
19-
)
20-
return responseContentTypesDiffer
21-
}
22-
23-
function generateExampleOptions(codeExamples) {
24-
const requestContentTypesDiffer = shouldShowRequestContentType(codeExamples)
25-
const responseContentTypesDiffer = shouldShowResponseContentType(codeExamples)
26-
const showExampleOptionMediaType = responseContentTypesDiffer || requestContentTypesDiffer
27-
28-
return codeExamples.map((example, index) => {
29-
const requestContentType = example.request?.contentType
30-
const responseContentType = example.response?.contentType
31-
32-
let text = example.request?.description || `Example ${index + 1}`
33-
34-
if (showExampleOptionMediaType) {
35-
if (requestContentTypesDiffer && responseContentTypesDiffer) {
36-
// Show both request and response content types
37-
text = `${text} (${requestContentType}${responseContentType})`
38-
} else if (requestContentTypesDiffer) {
39-
// Show only request content type
40-
text = `${text} (${requestContentType})`
41-
} else if (responseContentTypesDiffer) {
42-
// Show only response content type
43-
text = `${text} (${responseContentType})`
44-
}
45-
}
46-
47-
return text
48-
})
49-
}
50-
519
test('detects request content types differ correctly', () => {
5210
const codeExamples = [
5311
{
54-
request: { contentType: 'text/plain', description: 'Example' },
12+
description: 'Example',
13+
request: { contentType: 'text/plain' },
5514
response: { contentType: 'text/html' },
5615
},
5716
{
58-
request: { contentType: 'text/x-markdown', description: 'Rendering markdown' },
17+
description: 'Rendering markdown',
18+
request: { contentType: 'text/x-markdown' },
5919
response: { contentType: 'text/html' },
6020
},
6121
]
@@ -67,11 +27,13 @@ describe('Request Content Type Logic', () => {
6727
test('detects response content types differ correctly', () => {
6828
const codeExamples = [
6929
{
70-
request: { contentType: 'application/json', description: 'JSON example' },
30+
description: 'JSON example',
31+
request: { contentType: 'application/json' },
7132
response: { contentType: 'application/json' },
7233
},
7334
{
74-
request: { contentType: 'application/json', description: 'Another JSON example' },
35+
description: 'Another JSON example',
36+
request: { contentType: 'application/json' },
7537
response: { contentType: 'text/html' },
7638
},
7739
]
@@ -83,53 +45,53 @@ describe('Request Content Type Logic', () => {
8345
test('generates correct options for markdown/raw scenario', () => {
8446
const markdownRawExamples = [
8547
{
48+
description: 'Example',
8649
request: {
8750
contentType: 'text/plain',
88-
description: 'Example',
8951
},
9052
response: {
9153
contentType: 'text/html',
9254
},
9355
},
9456
{
57+
description: 'Rendering markdown',
9558
request: {
9659
contentType: 'text/x-markdown',
97-
description: 'Rendering markdown',
9860
},
9961
response: {
10062
contentType: 'text/html',
10163
},
10264
},
10365
]
10466

105-
const options = generateExampleOptions(markdownRawExamples)
67+
const options = generateExampleOptionTexts(markdownRawExamples)
10668

10769
expect(options).toEqual(['Example (text/plain)', 'Rendering markdown (text/x-markdown)'])
10870
})
10971

11072
test('generates correct options when both request and response differ', () => {
11173
const mixedExamples = [
11274
{
75+
description: 'JSON request',
11376
request: {
11477
contentType: 'application/json',
115-
description: 'JSON request',
11678
},
11779
response: {
11880
contentType: 'application/json',
11981
},
12082
},
12183
{
84+
description: 'Plain text request',
12285
request: {
12386
contentType: 'text/plain',
124-
description: 'Plain text request',
12587
},
12688
response: {
12789
contentType: 'text/html',
12890
},
12991
},
13092
]
13193

132-
const options = generateExampleOptions(mixedExamples)
94+
const options = generateExampleOptionTexts(mixedExamples)
13395

13496
expect(options).toEqual([
13597
'JSON request (application/json → application/json)',
@@ -140,36 +102,36 @@ describe('Request Content Type Logic', () => {
140102
test('does not show content types when they are all the same', () => {
141103
const sameContentTypeExamples = [
142104
{
105+
description: 'First example',
143106
request: {
144107
contentType: 'application/json',
145-
description: 'First example',
146108
},
147109
response: {
148110
contentType: 'application/json',
149111
},
150112
},
151113
{
114+
description: 'Second example',
152115
request: {
153116
contentType: 'application/json',
154-
description: 'Second example',
155117
},
156118
response: {
157119
contentType: 'application/json',
158120
},
159121
},
160122
]
161123

162-
const options = generateExampleOptions(sameContentTypeExamples)
124+
const options = generateExampleOptionTexts(sameContentTypeExamples)
163125

164126
expect(options).toEqual(['First example', 'Second example'])
165127
})
166128

167129
test('handles single example correctly', () => {
168130
const singleExample = [
169131
{
132+
description: 'Only example',
170133
request: {
171134
contentType: 'application/json',
172-
description: 'Only example',
173135
},
174136
response: {
175137
contentType: 'application/json',
@@ -180,7 +142,7 @@ describe('Request Content Type Logic', () => {
180142
expect(shouldShowRequestContentType(singleExample)).toBe(false)
181143
expect(shouldShowResponseContentType(singleExample)).toBe(false)
182144

183-
const options = generateExampleOptions(singleExample)
145+
const options = generateExampleOptionTexts(singleExample)
184146
expect(options).toEqual(['Only example'])
185147
})
186148
})

0 commit comments

Comments
 (0)