Add OpenAPI Webhook block (#3160)

This commit is contained in:
Nolann B.
2025-04-18 08:35:44 +02:00
committed by GitHub
parent 3363a18856
commit d67699a1f1
31 changed files with 684 additions and 305 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': minor
'gitbook': minor
---
Add OpenAPI Webhook block
+13 -13
View File
@@ -26,7 +26,7 @@
"name": "@gitbook/cache-tags",
"version": "0.3.1",
"dependencies": {
"@gitbook/api": "^0.109.0",
"@gitbook/api": "^0.111.0",
"assert-never": "^1.2.1",
},
"devDependencies": {
@@ -259,7 +259,7 @@
},
"overrides": {
"@codemirror/state": "6.4.1",
"@gitbook/api": "0.109.0",
"@gitbook/api": "0.111.0",
"react": "18.3.1",
"react-dom": "18.3.1",
},
@@ -628,7 +628,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.109.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-nzhxmiNR2MMHh+HoU7fFdOJ+u4BFyXjtsVquQf3VhZMjeI71yeLx5JEbjkOcijVd/GMAtvpYlYR2qcPqzq27gw=="],
"@gitbook/api": ["@gitbook/api@0.111.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-E5Pk28kPD4p6XNWdwFM9pgDijdByseIZQqcFK+/hoW5tEZa5Yw/plRKJyN1hmwfPL6SKq6Maf0fbIzTQiVXyQQ=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -4032,7 +4032,7 @@
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"gitbook-v2/next": ["next@15.3.1-canary.7", "", { "dependencies": { "@next/env": "15.3.1-canary.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.7", "@next/swc-darwin-x64": "15.3.1-canary.7", "@next/swc-linux-arm64-gnu": "15.3.1-canary.7", "@next/swc-linux-arm64-musl": "15.3.1-canary.7", "@next/swc-linux-x64-gnu": "15.3.1-canary.7", "@next/swc-linux-x64-musl": "15.3.1-canary.7", "@next/swc-win32-arm64-msvc": "15.3.1-canary.7", "@next/swc-win32-x64-msvc": "15.3.1-canary.7", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RblvC7A7k8cKpSa6JSCbaatb6JAncv2niUSH7TqXt6xHUoItnxa3Fu7xkducFJQdcW2Hv2h0+NBu6kaKf0S4zw=="],
"gitbook-v2/next": ["next@15.3.1-canary.8", "", { "dependencies": { "@next/env": "15.3.1-canary.8", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.8", "@next/swc-darwin-x64": "15.3.1-canary.8", "@next/swc-linux-arm64-gnu": "15.3.1-canary.8", "@next/swc-linux-arm64-musl": "15.3.1-canary.8", "@next/swc-linux-x64-gnu": "15.3.1-canary.8", "@next/swc-linux-x64-musl": "15.3.1-canary.8", "@next/swc-win32-arm64-msvc": "15.3.1-canary.8", "@next/swc-win32-x64-msvc": "15.3.1-canary.8", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Of5a3BTTIl/iUvL2a9Jh7m7G/H8z4Pj5Vs54CLvcdadokxSNgLOpjzbDgFR8J4PawLx6+MOMy19m9Cvr6EPGug=="],
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
@@ -4908,23 +4908,23 @@
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.7", "", {}, "sha512-QSzAlzxaGrQYj3Nzgxd35WM6pmxmNk9lOCn0nsNLPidvc3H6wj/fV3Pl8/tVCYQP61luGuA0Ib0VwZc8ja119w=="],
"gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.8", "", {}, "sha512-ShZTo0hNhbTRrp7k6oUDSck4Xx4hhfSeLBp35jvGaw1QMZzWYr5v/oc0kEt0bfMdl+833flwKV7kFR3BnrULfg=="],
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fs7JYU0MKtpgsDBIvbT/wn1MwKP8C+bqcb6IjWnDtVcciFKzYDs/tPCBW6CtYg1wpHqm9horZDA1ytb/sAghxA=="],
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZaDynM+pbrnLLlBAxH/CDGp9KN79OFrLcT1ejlWyo86V3SS9Gyqr4nmTuvTevByTTpr1VHReQel8Zbq0Pttu7Q=="],
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Efl6es/IEXJf5groF78fc1PrTEwK9bOyi6fRjp1bsa/AWOHp8rTc3l0T6EeY5T0SWeYilisZZa9rax7nTgw4bw=="],
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-BsMR8WqeCDAX8C9RYAO8TI4ttpuqKk2oYMb1+bCrOYi857SMfB4vWD4PWmMvj7mwFXGqrxly4W6CBQlD66A+fg=="],
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-IS71I8z2wlecRZRHXqWTtxVozZt1h9IH/2MX1P/WkCjQH98acFmCkw0jo/y70mrZ4lw6tRGTNkSBMun+5P5P+w=="],
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-xL+K2SW+/46j/KnKNf1gizM1bxwcEaE56eCEG9RPoYS/lfxHLuHcR9O2MlcPI90g/rIN2HXmHeuKRbXbnVy15g=="],
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-uJ4env9fZxIFM+aRBCjNeG91HHZ2cRXiF25xJqGmCC1Y5jT5oYAB/tIBCFRSj6NRzdt4pZAamCMRS41WzzVVUQ=="],
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-bzXlCUXkjIRsMTb6rr7OsWEQmdO2rZgKijnMGBJzEpa9ROq95VJTF+rdyrLmHuc4fPsAGDn/C18V3E1YOi4ipQ=="],
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-/+efTL2TxoSAYCxdf+g6WvDKIJDoaETvF3I+lxpixhctfrIwpr2qnG0e6u2M55k1iYiiyFIOA63fpIsGnIMJZA=="],
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-snbPQ9th7eoYYMZpNGhEvX4EbqGjjkiXzTEm2F/oDadlZR2wI6egtfQHiZqeczL7XwG3M66hBJ1/U20wdTDtvA=="],
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Hb+agfULOjEhJAmq/r9OTVAo8KxFlnuCT7BEUbSuVP/Gxn883al6juSG7ZZfk7P4a+ZU6iVYyHKIFAKCarZmUA=="],
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-5hmcaGazc3w6rg/gbQOrmuw0kKShf4Egs0JlUPop/ZiRDfZO5KlyrGY0hUD+2pLG7Yx3B/fTbj86cHKfQZDMBw=="],
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-gNd5Vq7k9xIEQj85JoavHGRsyvu1zKOjVoPUYtsZRxXw6kcBg84x2g8NeT/3SmwH1PiFU/sjQXtRzr2Kuc2j3A=="],
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-t6uwWC/UbQ8CQGyBVbEmpcJC41yAvz2eZZGec9EoO0ZyQ/fbvjOkqfPnf+WcjWifWNeuiWogjN1UBg7YK9IaVA=="],
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5i+KinvNNmspXfgIvEZ1RFOKVhOgdtoSC7/xEUm55hEkcWFVHOtVVPvgV7WUeo1elNxbgxytzmyKbLaifP03g=="],
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "x64" }, "sha512-93gNqVYwlr9bz6Pm5dQnqjpuOEQyvDre0+H39UqS7h2KuFh3sf2MejhAEr2uFEux4mb7TN0PlohaihO7YxJ3fw=="],
"gitbook-v2/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
+1 -1
View File
@@ -12,7 +12,7 @@
"@codemirror/state": "6.4.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"@gitbook/api": "0.109.0"
"@gitbook/api": "0.111.0"
},
"private": true,
"scripts": {
+1 -1
View File
@@ -10,7 +10,7 @@
},
"version": "0.3.1",
"dependencies": {
"@gitbook/api": "^0.109.0",
"@gitbook/api": "^0.111.0",
"assert-never": "^1.2.1"
},
"devDependencies": {
@@ -25,7 +25,7 @@ import { IntegrationBlock } from './Integration';
import { List } from './List';
import { ListItem } from './ListItem';
import { BlockMath } from './Math';
import { OpenAPIOperation, OpenAPISchemas } from './OpenAPI';
import { OpenAPIOperation, OpenAPISchemas, OpenAPIWebhook } from './OpenAPI';
import { Paragraph } from './Paragraph';
import { Quote } from './Quote';
import { ReusableContent } from './ReusableContent';
@@ -85,6 +85,8 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
return <OpenAPIOperation {...props} block={block} />;
case 'openapi-schemas':
return <OpenAPISchemas {...props} block={block} />;
case 'openapi-webhook':
return <OpenAPIWebhook {...props} block={block} />;
case 'embed':
return <Embed {...props} block={block} />;
case 'blockquote':
@@ -159,6 +161,7 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
case 'swagger':
case 'openapi-operation':
case 'openapi-schemas':
case 'openapi-webhook':
case 'math':
case 'divider':
case 'content-ref':
@@ -0,0 +1,58 @@
import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
import { tcls } from '@/lib/tailwind';
import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
import { getOpenAPIContext } from './context';
/**
* Render an openapi block or an openapi-webhook block.
*/
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
const { style } = props;
return (
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
<OpenAPIWebhookBody {...props} />
</div>
);
}
async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
const { block, context } = props;
if (!context.contentContext) {
return null;
}
const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
block,
context: context.contentContext,
});
if (error) {
return (
<div className="hidden">
<p>
Error with {specUrl}: {error.message}
</p>
</div>
);
}
if (!data || !specUrl) {
return null;
}
return (
<BaseOpenAPIWebhook
data={data}
context={getOpenAPIContext({ props, specUrl })}
className="openapi-block"
/>
);
}
@@ -11,13 +11,17 @@ import { Heading } from '../Heading';
import './scalar.css';
import './style.css';
import type { AnyOpenAPIOperationsBlock, OpenAPISchemasBlock } from '@/lib/openapi/types';
import type {
AnyOpenAPIOperationsBlock,
OpenAPISchemasBlock,
OpenAPIWebhookBlock,
} from '@/lib/openapi/types';
/**
* Get the OpenAPI context to render a block.
*/
export function getOpenAPIContext(args: {
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>;
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
specUrl: string;
}): OpenAPIContext {
const { props, specUrl } = args;
@@ -1,2 +1,3 @@
export * from './OpenAPIOperation';
export * from './OpenAPISchemas';
export * from './OpenAPIWebhook';
@@ -1,6 +1,7 @@
/* Layout Components */
.openapi-operation,
.openapi-schemas {
.openapi-schemas,
.openapi-webhook {
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
}
+2 -1
View File
@@ -5,6 +5,7 @@ import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cac
import type {
AnyOpenAPIOperationsBlock,
OpenAPISchemasBlock,
OpenAPIWebhookBlock,
ResolveOpenAPIBlockArgs,
} from '@/lib/openapi/types';
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
@@ -16,7 +17,7 @@ import { isV2 } from '../v2';
import { enrichFilesystem } from './enrich';
import type { FetchOpenAPIFilesystemResult } from './types';
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock;
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock;
/**
* Fetch OpenAPI block.
@@ -0,0 +1,61 @@
import { fetchOpenAPIFilesystem } from '@/lib/openapi/fetch';
import { OpenAPIParseError } from '@gitbook/openapi-parser';
import { type OpenAPIWebhookData, resolveOpenAPIWebhook } from '@gitbook/react-openapi';
import type {
OpenAPIWebhookBlock,
ResolveOpenAPIBlockArgs,
ResolveOpenAPIBlockResult,
} from './types';
type ResolveOpenAPIWebhookBlockResult = ResolveOpenAPIBlockResult<OpenAPIWebhookData>;
const weakmap = new WeakMap<OpenAPIWebhookBlock, Promise<ResolveOpenAPIWebhookBlockResult>>();
/**
* Cache the result of resolving an OpenAPI block.
* It is important because the resolve is called in sections and in the block itself.
*/
export function resolveOpenAPIWebhookBlock(
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
): Promise<ResolveOpenAPIWebhookBlockResult> {
if (weakmap.has(args.block)) {
return weakmap.get(args.block)!;
}
const result = baseResolveOpenAPIWebhookBlock(args);
weakmap.set(args.block, result);
return result;
}
/**
* Resolve OpenAPI webhook block.
*/
async function baseResolveOpenAPIWebhookBlock(
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
): Promise<ResolveOpenAPIWebhookBlockResult> {
const { context, block } = args;
if (!block.data.name || !block.data.method) {
return { data: null, specUrl: null };
}
try {
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
if (!filesystem) {
return { data: null, specUrl: null };
}
const data = await resolveOpenAPIWebhook(filesystem, {
name: block.data.name,
method: block.data.method,
});
return { data, specUrl };
} catch (error) {
if (error instanceof OpenAPIParseError) {
return { error };
}
throw error;
}
}
@@ -2,6 +2,7 @@ import type {
DocumentBlockOpenAPI,
DocumentBlockOpenAPIOperation,
DocumentBlockOpenAPISchemas,
DocumentBlockOpenAPIWebhook,
} from '@gitbook/api';
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
import type { GitBookAnyContext } from '@v2/lib/context';
@@ -16,6 +17,11 @@ export type AnyOpenAPIOperationsBlock = DocumentBlockOpenAPI | DocumentBlockOpen
*/
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
/**
* Type for OpenAPI Webhook block
*/
export type OpenAPIWebhookBlock = DocumentBlockOpenAPIWebhook;
/**
* Arguments for resolving OpenAPI block.
*/
@@ -22,7 +22,7 @@ export function OpenAPICodeSample(props: {
data: OpenAPIOperationData;
context: OpenAPIContext;
}) {
const { data } = props;
const { data, context } = props;
// If code samples are disabled at operation level, we don't display the code samples.
if (data.operation['x-codeSamples'] === false) {
@@ -43,7 +43,9 @@ export function OpenAPICodeSample(props: {
return null;
}
return <OpenAPICodeSampleBody data={data} items={samples} />;
return (
<OpenAPICodeSampleBody data={data} items={samples} selectIcon={context.icons.chevronDown} />
);
}
/**
@@ -146,6 +148,7 @@ function generateCodeSamples(props: {
method={data.method}
path={data.path}
renderers={renderers}
blockKey={context.blockKey}
/>
),
footer: (
@@ -207,6 +210,7 @@ function OpenAPICodeSampleFooter(props: {
path={data.path}
renderers={renderers}
selectIcon={context.icons.chevronDown}
blockKey={context.blockKey}
/>
) : (
<span />
@@ -2,18 +2,20 @@
import clsx from 'clsx';
import type { MediaTypeRenderer } from './OpenAPICodeSample';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { createStateKey } from './utils';
export function OpenAPIMediaTypeExamplesSelector(props: {
method: string;
path: string;
renderers: MediaTypeRenderer[];
selectIcon?: React.ReactNode;
blockKey?: string;
}) {
const { method, path, renderers, selectIcon } = props;
const { method, path, renderers, selectIcon, blockKey } = props;
if (!renderers[0]) {
throw new Error('No renderers provided');
}
const stateKey = `media-type-${method}-${path}`;
const stateKey = createStateKey('request-body-media-type', blockKey);
const state = useSelectState(stateKey, renderers[0].mediaType);
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
@@ -102,13 +104,17 @@ export function OpenAPIMediaTypeExamplesBody(props: {
method: string;
path: string;
renderers: MediaTypeRenderer[];
blockKey?: string;
}) {
const { renderers, method, path } = props;
const { renderers, method, path, blockKey } = props;
if (!renderers[0]) {
throw new Error('No renderers provided');
}
const mediaTypeState = useSelectState(`media-type-${method}-${path}`, renderers[0].mediaType);
const mediaTypeState = useSelectState(
createStateKey('request-body-media-type', blockKey),
renderers[0].mediaType
);
const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
if (selected.examples.length === 0) {
return selected.element;
@@ -1,9 +1,7 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { generateSchemaExample } from './generateSchemaExample';
import { json2xml } from './json2xml';
import { stringifyOpenAPI } from './stringifyOpenAPI';
import type { OpenAPIContext } from './types';
import { checkIsReference } from './utils';
/**
* Display an example.
@@ -51,79 +49,3 @@ export function OpenAPIEmptyExample() {
</pre>
);
}
/**
* Generate an example from a reference object.
*/
export function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
}
/**
* Get examples from a media type object.
*/
export function getExamplesFromMediaTypeObject(args: {
mediaType: string;
mediaTypeObject: OpenAPIV3.MediaTypeObject;
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
const { mediaTypeObject, mediaType } = args;
if (mediaTypeObject.examples) {
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
return {
key,
example: checkIsReference(example) ? getExampleFromReference(example) : example,
};
});
}
if (mediaTypeObject.example) {
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
}
if (mediaTypeObject.schema) {
if (mediaType === 'application/xml') {
// @TODO normally we should use the name of the schema but we don't have it
// fix it when we got the reference name
const root = mediaTypeObject.schema.xml?.name ?? 'object';
return [
{
key: 'default',
example: {
value: {
[root]: generateSchemaExample(mediaTypeObject.schema, {
xml: mediaType === 'application/xml',
mode: 'read',
}),
},
},
},
];
}
return [
{
key: 'default',
example: {
value: generateSchemaExample(mediaTypeObject.schema, {
mode: 'read',
}),
},
},
];
}
return [];
}
/**
* Get example from a schema object.
*/
export function getExampleFromSchema(args: {
schema: OpenAPIV3.SchemaObject;
}): OpenAPIV3.ExampleObject {
const { schema } = args;
if (schema.example) {
return { value: schema.example };
}
return { value: generateSchemaExample(schema, { mode: 'read' }) };
}
@@ -3,37 +3,30 @@ import type { Key } from 'react-aria';
import { OpenAPIEmptyExample } from './OpenAPIExample';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { StaticSection } from './StaticSection';
import { createStateKey } from './utils';
type OpenAPIResponseMediaTypeItem = OpenAPISelectItem & {
type OpenAPIMediaTypeItem = OpenAPISelectItem & {
body: React.ReactNode;
examples?: OpenAPIResponseMediaTypeItem[];
examples?: OpenAPIMediaTypeItem[];
};
/**
* Get the state of the response examples select.
*/
export function useResponseMediaTypesState(
blockKey: string | undefined,
initialKey: Key = 'default'
) {
return useSelectState(getResponseMediaTypeStateKey(blockKey), initialKey);
export function useMediaTypesState(stateKey: string | undefined, initialKey: Key = 'default') {
return useSelectState(stateKey, initialKey);
}
function useResponseMediaTypeExamplesState(
blockKey: string | undefined,
initialKey: Key = 'default'
) {
return useSelectState(getResponseMediaTypeExamplesStateKey(blockKey), initialKey);
function useMediaTypeExamplesState(stateKey: string | undefined, initialKey: Key = 'default') {
return useSelectState(stateKey, initialKey);
}
export function OpenAPIResponseMediaTypeContent(props: {
items: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
export function OpenAPIMediaTypeContent(props: {
items: OpenAPIMediaTypeItem[];
selectIcon?: React.ReactNode;
stateKey: string;
}) {
const { blockKey, items, selectIcon } = props;
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
const { stateKey, items, selectIcon } = props;
const state = useMediaTypesState(stateKey, items[0]?.key);
const examples = items.find((item) => item.key === state.key)?.examples ?? [];
@@ -45,28 +38,28 @@ export function OpenAPIResponseMediaTypeContent(props: {
<StaticSection
footer={
items.length > 1 || examples.length > 1 ? (
<OpenAPIResponseMediaTypeFooter
blockKey={blockKey}
<OpenAPIMediaTypeFooter
items={items}
examples={examples}
selectIcon={selectIcon}
stateKey={stateKey}
/>
) : null
}
className="openapi-response-media-types-examples"
>
<OpenAPIResponseMediaTypeBody blockKey={blockKey} items={items} examples={examples} />
<OpenAPIMediaTypeBody stateKey={stateKey} items={items} examples={examples} />
</StaticSection>
);
}
function OpenAPIResponseMediaTypeFooter(props: {
items: OpenAPIResponseMediaTypeItem[];
examples?: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
function OpenAPIMediaTypeFooter(props: {
items: OpenAPIMediaTypeItem[];
examples?: OpenAPIMediaTypeItem[];
selectIcon?: React.ReactNode;
stateKey: string;
}) {
const { items, examples, blockKey, selectIcon } = props;
const { items, examples, stateKey, selectIcon } = props;
return (
<>
@@ -74,7 +67,7 @@ function OpenAPIResponseMediaTypeFooter(props: {
<OpenAPISelect
icon={selectIcon}
items={items}
stateKey={getResponseMediaTypeStateKey(blockKey)}
stateKey={stateKey}
placement="bottom start"
>
{items.map((item) => (
@@ -89,7 +82,7 @@ function OpenAPIResponseMediaTypeFooter(props: {
<OpenAPISelect
icon={selectIcon}
items={examples}
stateKey={getResponseMediaTypeExamplesStateKey(blockKey)}
stateKey={`${stateKey}-examples`}
placement="bottom start"
>
{examples.map((example) => (
@@ -103,18 +96,18 @@ function OpenAPIResponseMediaTypeFooter(props: {
);
}
function OpenAPIResponseMediaTypeBody(props: {
items: OpenAPIResponseMediaTypeItem[];
examples?: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
function OpenAPIMediaTypeBody(props: {
items: OpenAPIMediaTypeItem[];
examples?: OpenAPIMediaTypeItem[];
stateKey: string;
}) {
const { blockKey, items, examples } = props;
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
const { stateKey, items, examples } = props;
const state = useMediaTypesState(stateKey, items[0]?.key);
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
const exampleState = useResponseMediaTypeExamplesState(
blockKey,
const exampleState = useMediaTypeExamplesState(
`${stateKey}-examples`,
selectedItem?.examples?.[0]?.key
);
@@ -135,14 +128,3 @@ function OpenAPIResponseMediaTypeBody(props: {
return selectedItem.body;
}
/**
* Return the state key for the response media types.
*/
function getResponseMediaTypeStateKey(blockKey: string | undefined) {
return createStateKey('response-media-types', blockKey);
}
function getResponseMediaTypeExamplesStateKey(blockKey: string | undefined) {
return createStateKey('response-media-types-examples', blockKey);
}
@@ -1,18 +1,9 @@
import clsx from 'clsx';
import type {
OpenAPICustomOperationProperties,
OpenAPIStability,
OpenAPIV3,
} from '@gitbook/openapi-parser';
import { Markdown } from './Markdown';
import { OpenAPICodeSample } from './OpenAPICodeSample';
import { OpenAPIPath } from './OpenAPIPath';
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
import { OpenAPISpec } from './OpenAPISpec';
import { getOpenAPIClientContext } from './context';
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
import { OpenAPISummary } from './common/OpenAPISummary';
import type { OpenAPIContext, OpenAPIOperationData } from './types';
import { resolveDescription } from './utils';
/**
* Display an interactive OpenAPI operation.
@@ -23,46 +14,12 @@ export function OpenAPIOperation(props: {
context: OpenAPIContext;
}) {
const { className, data, context } = props;
const { operation } = data;
const clientContext = getOpenAPIClientContext(context);
return (
<div className={clsx('openapi-operation', className)}>
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
{(operation.deprecated || operation['x-stability']) && (
<div className="openapi-summary-tags">
{operation.deprecated && (
<div className="openapi-deprecated">Deprecated</div>
)}
{operation['x-stability'] && (
<OpenAPIOperationStability stability={operation['x-stability']} />
)}
</div>
)}
{operation.summary
? context.renderHeading({
deprecated: operation.deprecated ?? false,
stability: operation['x-stability'],
title: operation.summary,
})
: null}
<OpenAPIPath data={data} />
</div>
<OpenAPISummary data={data} context={context} />
<div className="openapi-columns">
<div className="openapi-column-spec">
{operation['x-deprecated-sunset'] ? (
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
This operation is deprecated and will be sunset on{' '}
<span className="openapi-deprecated-sunset-date">
{operation['x-deprecated-sunset']}
</span>
{'.'}
</div>
) : null}
<OpenAPIOperationDescription operation={operation} context={context} />
<OpenAPISpec data={data} context={clientContext} />
</div>
<OpenAPIColumnSpec data={data} context={context} />
<div className="openapi-column-preview">
<div className="openapi-column-preview-body">
<OpenAPICodeSample {...props} />
@@ -73,52 +30,3 @@ export function OpenAPIOperation(props: {
</div>
);
}
function OpenAPIOperationDescription(props: {
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
context: OpenAPIContext;
}) {
const { operation } = props;
if (operation['x-gitbook-description-document']) {
return (
<div className="openapi-intro">
{props.context.renderDocument({
document: operation['x-gitbook-description-document'],
})}
</div>
);
}
const description = resolveDescription(operation);
if (!description) {
return null;
}
return (
<div className="openapi-intro">
<Markdown className="openapi-description" source={description} />
</div>
);
}
const stabilityEnum = {
experimental: 'Experimental',
alpha: 'Alpha',
beta: 'Beta',
} as const;
function OpenAPIOperationStability(props: { stability: OpenAPIStability }) {
const { stability } = props;
const foundStability = stabilityEnum[stability];
if (!foundStability) {
return null;
}
return (
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
{foundStability}
</div>
);
}
@@ -1,8 +1,8 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { InteractiveSection } from './InteractiveSection';
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
import { checkIsReference } from './utils';
import type { OpenAPIClientContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { checkIsReference, createStateKey } from './utils';
/**
* Display an interactive request body.
@@ -10,20 +10,21 @@ import { checkIsReference } from './utils';
export function OpenAPIRequestBody(props: {
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
context: OpenAPIClientContext;
data: OpenAPIOperationData;
data: OpenAPIOperationData | OpenAPIWebhookData;
}) {
const { requestBody, context, data } = props;
const { method, path } = data;
if (checkIsReference(requestBody)) {
return null;
}
const header = 'name' in data ? 'Payload' : 'Body';
return (
<InteractiveSection
header="Body"
header={header}
className="openapi-requestbody"
stateKey={`media-type-${method}-${path}`}
stateKey={createStateKey('request-body-media-type', context.blockKey)}
selectIcon={context.icons.chevronDown}
tabs={Object.entries(requestBody.content ?? {}).map(
([contentType, mediaTypeObject]) => {
@@ -1,22 +1,18 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { Markdown } from './Markdown';
import {
OpenAPIEmptyExample,
OpenAPIExample,
getExampleFromReference,
getExamplesFromMediaTypeObject,
} from './OpenAPIExample';
import { OpenAPIEmptyExample, OpenAPIExample } from './OpenAPIExample';
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
import { OpenAPIResponseMediaTypeContent } from './OpenAPIResponseMediaType';
import type { OpenAPIContext, OpenAPIOperationData } from './types';
import { getStatusCodeDefaultLabel } from './utils';
import type { OpenAPIContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { getExampleFromReference, getExamples } from './util/example';
import { createStateKey, getStatusCodeDefaultLabel } from './utils';
import { checkIsReference, resolveDescription } from './utils';
/**
* Display an example of the response content.
*/
export function OpenAPIResponseExample(props: {
data: OpenAPIOperationData;
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
@@ -127,45 +123,10 @@ function OpenAPIResponse(props: {
});
return (
<OpenAPIResponseMediaTypeContent
<OpenAPIMediaTypeContent
selectIcon={context.icons.chevronDown}
blockKey={context.blockKey}
stateKey={createStateKey('response-media-types', context.blockKey)}
items={tabs}
/>
);
}
function getExamples(props: {
mediaTypeObject: OpenAPIV3.MediaTypeObject;
mediaType: string;
context: OpenAPIContext;
}) {
const { mediaTypeObject, mediaType } = props;
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
const syntax = getSyntaxFromMediaType(mediaType);
return examples.map((example) => {
return {
key: example.key,
label: example.example.summary || example.key,
body: (
<OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
),
};
});
}
/**
* Get the syntax from a media type.
*/
function getSyntaxFromMediaType(mediaType: string): string {
if (mediaType.includes('json')) {
return 'json';
}
if (mediaType === 'application/xml') {
return 'xml';
}
return 'text';
}
+8 -3
View File
@@ -5,17 +5,22 @@ import { OpenAPIResponses } from './OpenAPIResponses';
import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
import { OpenAPISecurities } from './OpenAPISecurities';
import { StaticSection } from './StaticSection';
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
import type { OpenAPIClientContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { parameterToProperty } from './utils';
export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAPIClientContext }) {
export function OpenAPISpec(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIClientContext;
}) {
const { data, context } = props;
const { operation, securities } = data;
const { operation } = data;
const parameters = operation.parameters ?? [];
const parameterGroups = groupParameters(parameters);
const securities = 'securities' in data ? data.securities : [];
return (
<>
{securities.length > 0 ? (
@@ -0,0 +1,30 @@
import clsx from 'clsx';
import { OpenAPIWebhookExample } from './OpenAPIWebhookExample';
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
import { OpenAPISummary } from './common/OpenAPISummary';
import type { OpenAPIContext, OpenAPIWebhookData } from './types';
/**
* Display an interactive OpenAPI webhook.
*/
export function OpenAPIWebhook(props: {
className?: string;
data: OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { className, data, context } = props;
return (
<div className={clsx('openapi-webhook', className)}>
<OpenAPISummary data={data} context={context} />
<div className="openapi-columns">
<OpenAPIColumnSpec data={data} context={context} />
<div className="openapi-column-preview">
<div className="openapi-column-preview-body">
<OpenAPIWebhookExample data={data} context={context} />
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,59 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { OpenAPIEmptyExample } from './OpenAPIExample';
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
import type { OpenAPIContext } from './types';
import type { OpenAPIWebhookData } from './types';
import { getExamples } from './util/example';
import { createStateKey } from './utils';
export function OpenAPIWebhookExample(props: {
data: OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const items = (() => {
if (!operation.requestBody) {
return [];
}
return Object.entries(
operation.requestBody.content as Record<string, OpenAPIV3.MediaTypeObject>
).map(([key, value]) => {
const schema = value.schema;
if (!schema) {
return {
key,
label: key,
body: <OpenAPIEmptyExample />,
};
}
return {
key,
label: key,
body: <></>,
examples: getExamples({
mediaTypeObject: value,
mediaType: key,
context,
}),
};
});
})();
return (
<div className="openapi-panel">
<h4 className="openapi-panel-heading">Payload</h4>
<div className="openapi-panel-body">
<OpenAPIMediaTypeContent
selectIcon={context.icons.chevronDown}
stateKey={createStateKey('request-body-media-type', context.blockKey)}
items={items}
/>
</div>
</div>
);
}
@@ -0,0 +1,30 @@
import { OpenAPISpec } from '../OpenAPISpec';
import { getOpenAPIClientContext } from '../context';
import type { OpenAPIContext, OpenAPIOperationData, OpenAPIWebhookData } from '../types';
import { OpenAPIOperationDescription } from './OpenAPIOperationDescription';
export function OpenAPIColumnSpec(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const clientContext = getOpenAPIClientContext(context);
return (
<div className="openapi-column-spec">
{operation['x-deprecated-sunset'] ? (
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
This operation is deprecated and will be sunset on{' '}
<span className="openapi-deprecated-sunset-date">
{operation['x-deprecated-sunset']}
</span>
{'.'}
</div>
) : null}
<OpenAPIOperationDescription operation={operation} context={context} />
<OpenAPISpec data={data} context={clientContext} />
</div>
);
}
@@ -0,0 +1,31 @@
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
import { Markdown } from '../Markdown';
import type { OpenAPIContext } from '../types';
import { resolveDescription } from '../utils';
export function OpenAPIOperationDescription(props: {
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
context: OpenAPIContext;
}) {
const { operation } = props;
if (operation['x-gitbook-description-document']) {
return (
<div className="openapi-intro">
{props.context.renderDocument({
document: operation['x-gitbook-description-document'],
})}
</div>
);
}
const description = resolveDescription(operation);
if (!description) {
return null;
}
return (
<div className="openapi-intro">
<Markdown className="openapi-description" source={description} />
</div>
);
}
@@ -0,0 +1,23 @@
import type { OpenAPIStability as OpenAPIStabilityType } from '@gitbook/openapi-parser';
const stabilityEnum: Record<OpenAPIStabilityType, string> = {
experimental: 'Experimental',
alpha: 'Alpha',
beta: 'Beta',
} as const;
export function OpenAPIStability(props: { stability: OpenAPIStabilityType }) {
const { stability } = props;
const foundStability = stabilityEnum[stability];
if (!foundStability) {
return null;
}
return (
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
{foundStability}
</div>
);
}
@@ -0,0 +1,44 @@
import { OpenAPIPath } from '../OpenAPIPath';
import type { OpenAPIContext, OpenAPIOperationData, OpenAPIWebhookData } from '../types';
import { OpenAPIStability } from './OpenAPIStability';
export function OpenAPISummary(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const title = (() => {
if (operation.summary) {
return operation.summary;
}
if ('name' in data) {
return data.name;
}
return undefined;
})();
return (
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
{(operation.deprecated || operation['x-stability']) && (
<div className="openapi-summary-tags">
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
{operation['x-stability'] && (
<OpenAPIStability stability={operation['x-stability']} />
)}
</div>
)}
{title
? context.renderHeading({
deprecated: operation.deprecated ?? false,
stability: operation['x-stability'],
title,
})
: null}
{'path' in data ? <OpenAPIPath data={data} /> : null}
</div>
);
}
+3 -1
View File
@@ -1,5 +1,7 @@
export * from './schemas';
export * from './OpenAPIOperation';
export * from './OpenAPIWebhook';
export * from './OpenAPIOperationContext';
export * from './resolveOpenAPIOperation';
export type { OpenAPIOperationData, OpenAPIContext } from './types';
export * from './resolveOpenAPIWebhook';
export type { OpenAPIOperationData, OpenAPIContext, OpenAPIWebhookData } from './types';
@@ -0,0 +1,99 @@
import { fromJSON, toJSON } from 'flatted';
import type {
Filesystem,
OpenAPIV3,
OpenAPIV3_1,
OpenAPIV3xDocument,
} from '@gitbook/openapi-parser';
import { dereferenceFilesystem } from './dereference';
import type { OpenAPIWebhookData } from './types';
export { fromJSON, toJSON };
/**
* Resolve an OpenAPI webhook in a file and compile it to a more usable format.
*/
export async function resolveOpenAPIWebhook(
filesystem: Filesystem<OpenAPIV3xDocument>,
webhookDescriptor: {
name: string;
method: string;
}
): Promise<OpenAPIWebhookData | null> {
const { name, method } = webhookDescriptor;
const schema = await dereferenceFilesystem(filesystem);
let operation = getWebhookByNameAndMethod(schema, name, method);
if (!operation) {
return null;
}
// Resolve common parameters
const commonParameters = getPathObjectParameter(schema, name);
if (commonParameters) {
operation = {
...operation,
parameters: [...commonParameters, ...(operation.parameters ?? [])],
};
}
const servers = 'servers' in schema ? (schema.servers ?? []) : [];
return {
servers,
operation,
method,
name,
};
}
/**
* Get a path object from its path.
*/
function getPathObject(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
name: string
): OpenAPIV3.PathItemObject | OpenAPIV3_1.PathItemObject | null {
if (schema.webhooks?.[name]) {
return schema.webhooks[name];
}
return null;
}
/**
* Resolve parameters from a path in an OpenAPI schema.
*/
function getPathObjectParameter(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
path: string
):
| (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[]
| (OpenAPIV3.ParameterObject | OpenAPIV3_1.ReferenceObject)[]
| null {
const pathObject = getPathObject(schema, path);
if (pathObject?.parameters) {
return pathObject.parameters;
}
return null;
}
/**
* Get an operation by its path and method.
*/
function getWebhookByNameAndMethod(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
name: string,
method: string
): OpenAPIV3.OperationObject | null {
// Types are buffy for OpenAPIV3_1.OperationObject, so we use v3
const pathObject = getPathObject(schema, name);
if (!pathObject) {
return null;
}
const normalizedMethod = method.toLowerCase();
if (!pathObject[normalizedMethod]) {
return null;
}
return pathObject[normalizedMethod];
}
@@ -1,11 +1,12 @@
import type { OpenAPISchema } from '@gitbook/openapi-parser';
import clsx from 'clsx';
import { OpenAPIDisclosure } from '../OpenAPIDisclosure';
import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample';
import { OpenAPIExample } from '../OpenAPIExample';
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
import { Section, SectionBody, StaticSection } from '../StaticSection';
import { getOpenAPIClientContext } from '../context';
import type { OpenAPIContext } from '../types';
import { getExampleFromSchema } from '../util/example';
/**
* OpenAPI Schemas component.
+11
View File
@@ -72,3 +72,14 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
/** Securities that should be used for this operation */
securities: [string, OpenAPISecurityWithRequired][];
}
export interface OpenAPIWebhookData extends OpenAPICustomSpecProperties {
name: string;
method: string;
/** Servers to be used for this operation */
servers: OpenAPIV3.ServerObject[];
/** Spec of the webhook */
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
}
+119
View File
@@ -0,0 +1,119 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { OpenAPIExample } from '../OpenAPIExample';
import { generateSchemaExample } from '../generateSchemaExample';
import type { OpenAPIContext } from '../types';
import { checkIsReference } from '../utils';
/**
* Generate an example from a reference object.
*/
export function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
}
/**
* Get examples from a media type object.
*/
export function getExamplesFromMediaTypeObject(args: {
mediaType: string;
mediaTypeObject: OpenAPIV3.MediaTypeObject;
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
const { mediaTypeObject, mediaType } = args;
if (mediaTypeObject.examples) {
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
return {
key,
example: checkIsReference(example) ? getExampleFromReference(example) : example,
};
});
}
if (mediaTypeObject.example) {
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
}
if (mediaTypeObject.schema) {
if (mediaType === 'application/xml') {
// @TODO normally we should use the name of the schema but we don't have it
// fix it when we got the reference name
const root = mediaTypeObject.schema.xml?.name ?? 'object';
return [
{
key: 'default',
example: {
value: {
[root]: generateSchemaExample(mediaTypeObject.schema, {
xml: mediaType === 'application/xml',
mode: 'read',
}),
},
},
},
];
}
return [
{
key: 'default',
example: {
value: generateSchemaExample(mediaTypeObject.schema, {
mode: 'read',
}),
},
},
];
}
return [];
}
/**
* Get example from a schema object.
*/
export function getExampleFromSchema(args: {
schema: OpenAPIV3.SchemaObject;
}): OpenAPIV3.ExampleObject {
const { schema } = args;
if (schema.example) {
return { value: schema.example };
}
return { value: generateSchemaExample(schema, { mode: 'read' }) };
}
/**
* Get the examples from a media type object.
*/
export function getExamples(props: {
mediaTypeObject: OpenAPIV3.MediaTypeObject;
mediaType: string;
context: OpenAPIContext;
}) {
const { mediaTypeObject, mediaType } = props;
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
const syntax = getSyntaxFromMediaType(mediaType);
return examples.map((example) => {
return {
key: example.key,
label: example.example.summary || example.key,
body: (
<OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
),
};
});
}
/**
* Get the syntax from a media type.
*/
function getSyntaxFromMediaType(mediaType: string): string {
if (mediaType.includes('json')) {
return 'json';
}
if (mediaType === 'application/xml') {
return 'xml';
}
return 'text';
}