mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
Use OpenAPI publicURL as Scalar specUrl (#4093)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@gitbook/react-openapi": patch
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Use OpenAPI publicURL as Scalar specUrl
|
||||||
@@ -26,7 +26,7 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, specUrl, error } = await resolveOpenAPIOperationBlock({
|
const { data, specUrl, publicURL, error } = await resolveOpenAPIOperationBlock({
|
||||||
block,
|
block,
|
||||||
context: context.contentContext,
|
context: context.contentContext,
|
||||||
});
|
});
|
||||||
@@ -46,7 +46,11 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
|
|||||||
return (
|
return (
|
||||||
<BaseOpenAPIOperation
|
<BaseOpenAPIOperation
|
||||||
data={data}
|
data={data}
|
||||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
context={getOpenAPIContext({
|
||||||
|
props,
|
||||||
|
specUrl: publicURL,
|
||||||
|
context: context.contentContext,
|
||||||
|
})}
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, specUrl, error } = await resolveOpenAPISchemasBlock({
|
const { data, specUrl, publicURL, error } = await resolveOpenAPISchemasBlock({
|
||||||
block,
|
block,
|
||||||
context: context.contentContext,
|
context: context.contentContext,
|
||||||
});
|
});
|
||||||
@@ -48,7 +48,11 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
|
|||||||
<BaseOpenAPISchemas
|
<BaseOpenAPISchemas
|
||||||
schemas={data.schemas}
|
schemas={data.schemas}
|
||||||
grouped={block.data.grouped}
|
grouped={block.data.grouped}
|
||||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
context={getOpenAPIContext({
|
||||||
|
props,
|
||||||
|
specUrl: publicURL,
|
||||||
|
context: context.contentContext,
|
||||||
|
})}
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
|
const { data, specUrl, publicURL, error } = await resolveOpenAPIWebhookBlock({
|
||||||
block,
|
block,
|
||||||
context: context.contentContext,
|
context: context.contentContext,
|
||||||
});
|
});
|
||||||
@@ -48,7 +48,11 @@ async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
|
|||||||
return (
|
return (
|
||||||
<BaseOpenAPIWebhook
|
<BaseOpenAPIWebhook
|
||||||
data={data}
|
data={data}
|
||||||
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
|
context={getOpenAPIContext({
|
||||||
|
props,
|
||||||
|
specUrl: publicURL,
|
||||||
|
context: context.contentContext,
|
||||||
|
})}
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type {
|
|||||||
*/
|
*/
|
||||||
export function getOpenAPIContext(args: {
|
export function getOpenAPIContext(args: {
|
||||||
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
|
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
|
||||||
specUrl: string;
|
specUrl: string | null;
|
||||||
context: GitBookAnyContext | undefined;
|
context: GitBookAnyContext | undefined;
|
||||||
}): OpenAPIContextInput {
|
}): OpenAPIContextInput {
|
||||||
const { props, specUrl, context } = args;
|
const { props, specUrl, context } = args;
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ export async function fetchOpenAPIFilesystem(
|
|||||||
const resolved = ref ? await resolveContentRef(ref, context) : null;
|
const resolved = ref ? await resolveContentRef(ref, context) : null;
|
||||||
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
return { filesystem: null, specUrl: null };
|
return { filesystem: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await (() => {
|
const result = await (() => {
|
||||||
// If the reference is a new OpenAPI reference, we return it.
|
// If the reference is a new OpenAPI reference, we return it.
|
||||||
if (ref.kind === 'openapi') {
|
if (ref.kind === 'openapi') {
|
||||||
assert(resolved.openAPIFilesystem);
|
assert(resolved.openapi?.filesystem);
|
||||||
return resolved.openAPIFilesystem;
|
return resolved.openapi.filesystem;
|
||||||
}
|
}
|
||||||
// For legacy blocks ("swagger"), we need to fetch the file system.
|
// For legacy blocks ("swagger"), we need to fetch the file system.
|
||||||
return fetchFilesystem(resolved.href, context.space.id);
|
return fetchFilesystem(resolved.href, context.space.id);
|
||||||
@@ -50,7 +50,21 @@ export async function fetchOpenAPIFilesystem(
|
|||||||
throw new OpenAPIParseError(result.error.message, { code: result.error.code });
|
throw new OpenAPIParseError(result.error.message, { code: result.error.code });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { filesystem: result, specUrl: resolved.href };
|
const publicURL = (() => {
|
||||||
|
// For new OpenAPI refs, use the explicit publicURL (null when spec is private).
|
||||||
|
if (ref.kind === 'openapi' && resolved.openapi) {
|
||||||
|
return resolved.openapi.publicURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For legacy "swagger" refs, the href is the public spec URL itself.
|
||||||
|
return resolved.href;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return {
|
||||||
|
filesystem: result,
|
||||||
|
specUrl: resolved.href,
|
||||||
|
publicURL,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ async function resolveOpenAPIOperationBlockNoCache(
|
|||||||
const { context, block } = args;
|
const { context, block } = args;
|
||||||
|
|
||||||
if (!block.data.path || !block.data.method) {
|
if (!block.data.path || !block.data.method) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
|
const { filesystem, specUrl, publicURL } = await fetchOpenAPIFilesystem({ block, context });
|
||||||
|
|
||||||
if (!filesystem) {
|
if (!filesystem) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await resolveOpenAPIOperation(filesystem, {
|
const data = await resolveOpenAPIOperation(filesystem, {
|
||||||
@@ -52,7 +52,7 @@ async function resolveOpenAPIOperationBlockNoCache(
|
|||||||
method: block.data.method,
|
method: block.data.method,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, specUrl };
|
return { data, specUrl, publicURL };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof OpenAPIParseError) {
|
if (error instanceof OpenAPIParseError) {
|
||||||
return { error };
|
return { error };
|
||||||
|
|||||||
@@ -37,21 +37,21 @@ async function baseResolveOpenAPISchemasBlock(
|
|||||||
): Promise<ResolveOpenAPISchemasBlockResult> {
|
): Promise<ResolveOpenAPISchemasBlockResult> {
|
||||||
const { context, block } = args;
|
const { context, block } = args;
|
||||||
if (!block.data.schemas || !block.data.schemas.length) {
|
if (!block.data.schemas || !block.data.schemas.length) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
|
const { filesystem, specUrl, publicURL } = await fetchOpenAPIFilesystem({ block, context });
|
||||||
|
|
||||||
if (!filesystem || !specUrl) {
|
if (!filesystem || !specUrl) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await resolveOpenAPISchemas(filesystem, {
|
const data = await resolveOpenAPISchemas(filesystem, {
|
||||||
schemas: block.data.schemas,
|
schemas: block.data.schemas,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, specUrl };
|
return { data, specUrl, publicURL };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof OpenAPIParseError) {
|
if (error instanceof OpenAPIParseError) {
|
||||||
return { error };
|
return { error };
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ async function baseResolveOpenAPIWebhookBlock(
|
|||||||
): Promise<ResolveOpenAPIWebhookBlockResult> {
|
): Promise<ResolveOpenAPIWebhookBlockResult> {
|
||||||
const { context, block } = args;
|
const { context, block } = args;
|
||||||
if (!block.data.name || !block.data.method) {
|
if (!block.data.name || !block.data.method) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
|
const { filesystem, specUrl, publicURL } = await fetchOpenAPIFilesystem({ block, context });
|
||||||
|
|
||||||
if (!filesystem) {
|
if (!filesystem) {
|
||||||
return { data: null, specUrl: null };
|
return { data: null, specUrl: null, publicURL: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await resolveOpenAPIWebhook(filesystem, {
|
const data = await resolveOpenAPIWebhook(filesystem, {
|
||||||
@@ -50,7 +50,7 @@ async function baseResolveOpenAPIWebhookBlock(
|
|||||||
method: block.data.method,
|
method: block.data.method,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, specUrl };
|
return { data, specUrl, publicURL };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof OpenAPIParseError) {
|
if (error instanceof OpenAPIParseError) {
|
||||||
return { error };
|
return { error };
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export type FetchOpenAPIFilesystemResult =
|
|||||||
error?: undefined;
|
error?: undefined;
|
||||||
filesystem: Filesystem<OpenAPIV3xDocument> | null;
|
filesystem: Filesystem<OpenAPIV3xDocument> | null;
|
||||||
specUrl: string | null;
|
specUrl: string | null;
|
||||||
|
/** Public URL of the OpenAPI spec, used as the spec URL for Scalar's "Test it" modal */
|
||||||
|
publicURL: string | null;
|
||||||
}
|
}
|
||||||
| FetchOpenAPIFilesystemError;
|
| FetchOpenAPIFilesystemError;
|
||||||
|
|
||||||
@@ -48,13 +50,14 @@ type FetchOpenAPIFilesystemError = {
|
|||||||
error: OpenAPIParseError;
|
error: OpenAPIParseError;
|
||||||
filesystem?: undefined;
|
filesystem?: undefined;
|
||||||
specUrl?: undefined;
|
specUrl?: undefined;
|
||||||
|
publicURL?: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolved OpenAPI block result.
|
* Resolved OpenAPI block result.
|
||||||
*/
|
*/
|
||||||
export type ResolveOpenAPIBlockResult<T> =
|
export type ResolveOpenAPIBlockResult<T> =
|
||||||
| { error?: undefined; data: T | null; specUrl: string | null }
|
| { error?: undefined; data: T | null; specUrl: string | null; publicURL: string | null }
|
||||||
| ResolveOpenAPIBlockError;
|
| ResolveOpenAPIBlockError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,4 +67,5 @@ type ResolveOpenAPIBlockError = {
|
|||||||
error: OpenAPIParseError;
|
error: OpenAPIParseError;
|
||||||
data?: undefined;
|
data?: undefined;
|
||||||
specUrl?: undefined;
|
specUrl?: undefined;
|
||||||
|
publicURL?: undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,12 +58,17 @@ export interface ResolvedContentRef {
|
|||||||
context: GitBookSpaceContext;
|
context: GitBookSpaceContext;
|
||||||
revisionReusableContent: RevisionReusableContent;
|
revisionReusableContent: RevisionReusableContent;
|
||||||
};
|
};
|
||||||
/** Resolve OpenAPI spec filesystem. */
|
|
||||||
openAPIFilesystem?: Filesystem;
|
|
||||||
/**
|
/**
|
||||||
* Space that the content ref belongs to (if applicable).
|
* Space that the content ref belongs to (if applicable).
|
||||||
*/
|
*/
|
||||||
space?: Space;
|
space?: Space;
|
||||||
|
/** Resolved OpenAPI spec, if the reference is an OpenAPI spec. */
|
||||||
|
openapi?: {
|
||||||
|
/** OpenAPI spec filesystem. */
|
||||||
|
filesystem: Filesystem;
|
||||||
|
/** Public URL of the OpenAPI spec */
|
||||||
|
publicURL: string | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolveContentRefOptions {
|
export interface ResolveContentRefOptions {
|
||||||
@@ -340,13 +345,13 @@ export async function resolveContentRef(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
// @ts-expect-error - Backward compatibility: `urls.source` replaces `url` in the next API version
|
href: openAPISpecVersionContent.urls.source,
|
||||||
href: openAPISpecVersionContent.urls?.source ?? openAPISpecVersionContent.url,
|
|
||||||
text: contentRef.spec,
|
text: contentRef.spec,
|
||||||
active: false,
|
active: false,
|
||||||
openAPIFilesystem: openAPISpecVersionContent.filesystem as Filesystem,
|
openapi: {
|
||||||
// @ts-expect-error - Public URL is not yet supported in the API
|
filesystem: openAPISpecVersionContent.filesystem as Filesystem,
|
||||||
publicURL: openAPISpecVersionContent.urls?.public,
|
publicURL: openAPISpecVersionContent.urls.public,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ function OpenAPICodeSampleFooter(props: {
|
|||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
)}
|
)}
|
||||||
{!hideTryItPanel && hasValidHost && (
|
{!hideTryItPanel && hasValidHost && specUrl && (
|
||||||
<ScalarApiButton
|
<ScalarApiButton
|
||||||
context={resolveScalarClientContext(context, servers, specUrl)}
|
context={resolveScalarClientContext(context, servers, specUrl)}
|
||||||
withProxy={Boolean(data.operation['x-enable-proxy'] ?? data['x-enable-proxy'])}
|
withProxy={Boolean(data.operation['x-enable-proxy'] ?? data['x-enable-proxy'])}
|
||||||
|
|||||||
@@ -67,9 +67,10 @@ export interface OpenAPIContext
|
|||||||
renderDocument: (props: { document: object }) => React.ReactNode;
|
renderDocument: (props: { document: object }) => React.ReactNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification URL.
|
* Public specification URL, used by Scalar's "Test it" modal.
|
||||||
|
* When null, the "Test it" button is hidden.
|
||||||
*/
|
*/
|
||||||
specUrl: string;
|
specUrl: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a signed proxy URL that restricts the proxy to specific origins.
|
* Build a signed proxy URL that restricts the proxy to specific origins.
|
||||||
|
|||||||
Reference in New Issue
Block a user