mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Add OpenAPI Webhook block (#3160)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': minor
|
||||
'gitbook': minor
|
||||
---
|
||||
|
||||
Add OpenAPI Webhook block
|
||||
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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' }) };
|
||||
}
|
||||
|
||||
+29
-47
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
Reference in New Issue
Block a user