diff --git a/.changeset/humble-insects-speak.md b/.changeset/humble-insects-speak.md new file mode 100644 index 000000000..bcb0f5f02 --- /dev/null +++ b/.changeset/humble-insects-speak.md @@ -0,0 +1,5 @@ +--- +"@gitbook/react-openapi": patch +--- + +Improve OpenAPI server URL validation diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index e51501b1f..1f40d5117 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -11,7 +11,7 @@ import { generateMediaTypeExamples, generateSchemaExample } from './generateSche import { stringifyOpenAPI } from './stringifyOpenAPI'; import type { OpenAPIOperationData } from './types'; import { mergeHeaders } from './util/headers'; -import { getDefaultServerURL } from './util/server'; +import { getDefaultServerURL, hasValidServerHost } from './util/server'; import { resolvePrefillCodePlaceholderFromSecurityScheme, resolveURLWithPrefillCodePlaceholdersFromServer, @@ -216,11 +216,14 @@ function OpenAPICodeSampleFooter(props: { const hasMultipleMediaTypes = renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0); + // Check if any server has a host that can be used in an HTTP request + const hasValidHost = hasValidServerHost(servers); + if (hideTryItPanel && !hasMultipleMediaTypes) { return null; } - if (!validateHttpMethod(method) || (!hasMultipleMediaTypes && servers.length === 0)) { + if (!validateHttpMethod(method) || (!hasMultipleMediaTypes && !hasValidHost)) { return null; } @@ -237,7 +240,7 @@ function OpenAPICodeSampleFooter(props: { ) : ( )} - {!hideTryItPanel && servers.length > 0 && ( + {!hideTryItPanel && hasValidHost && ( r.mediaType === state.key) || renderers[0]; + const hasMultipleMediaTypes = renderers.length >= 2; + const hasMultipleExamples = selected.examples.length >= 2; + + // Only render the wrapper div if at least one selector will render + if (!hasMultipleMediaTypes && !hasMultipleExamples) { + return null; + } + return (
diff --git a/packages/react-openapi/src/code-samples.ts b/packages/react-openapi/src/code-samples.ts index 7c357b4fb..f8be2d933 100644 --- a/packages/react-openapi/src/code-samples.ts +++ b/packages/react-openapi/src/code-samples.ts @@ -13,6 +13,7 @@ import { } from './contentTypeChecks'; import { json2xml } from './json2xml'; import { stringifyOpenAPI } from './stringifyOpenAPI'; +import { isValidServerHost } from './util/server'; export interface CodeSampleInput { method: string; @@ -69,9 +70,12 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [ const bodyString = body ? `\n${body}` : ''; + // Only include Host header if origin is considered a valid server host + const hasValidHost = isValidServerHost(origin); + const hostLine = hasValidHost ? `Host: ${origin.replaceAll(/https?:\/\//g, '')}\n` : ''; + const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1 -Host: ${origin.replaceAll(/https*:\/\//g, '')} -${headerString}${bodyString}`; +${hostLine}${headerString}${bodyString}`; return httpRequest; }, diff --git a/packages/react-openapi/src/util/server.ts b/packages/react-openapi/src/util/server.ts index f56970094..81379598f 100644 --- a/packages/react-openapi/src/util/server.ts +++ b/packages/react-openapi/src/util/server.ts @@ -45,3 +45,31 @@ function parseServerURL(url: string) { } return result; } + +/** + * Check if any server has a host that can be used in an HTTP request. + * This is used to determine if the "Try it" button should be shown. + */ +export function hasValidServerHost(servers: OpenAPIV3.ServerObject[]): boolean { + if (servers.length === 0) { + return false; + } + + return servers.some((server) => { + const url = interpolateServerURL(server); + return isValidServerHost(url); + }); +} + +/** + * Check if the server host/URL is valid for making direct HTTP requests. + * Accepts both full URLs (with protocol) and hostnames (without protocol). + */ +export function isValidServerHost(url: string): boolean { + // Check if URL starts with http:// or https:// + if (url.startsWith('http://') || url.startsWith('https://')) { + return true; + } + + return /^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(url); +}