mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 01:25:16 +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",
|
"name": "@gitbook/cache-tags",
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "^0.109.0",
|
"@gitbook/api": "^0.111.0",
|
||||||
"assert-never": "^1.2.1",
|
"assert-never": "^1.2.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@gitbook/api": "0.109.0",
|
"@gitbook/api": "0.111.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "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=="],
|
"@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"],
|
"@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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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",
|
"@codemirror/state": "6.4.1",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"@gitbook/api": "0.109.0"
|
"@gitbook/api": "0.111.0"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "^0.109.0",
|
"@gitbook/api": "^0.111.0",
|
||||||
"assert-never": "^1.2.1"
|
"assert-never": "^1.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { IntegrationBlock } from './Integration';
|
|||||||
import { List } from './List';
|
import { List } from './List';
|
||||||
import { ListItem } from './ListItem';
|
import { ListItem } from './ListItem';
|
||||||
import { BlockMath } from './Math';
|
import { BlockMath } from './Math';
|
||||||
import { OpenAPIOperation, OpenAPISchemas } from './OpenAPI';
|
import { OpenAPIOperation, OpenAPISchemas, OpenAPIWebhook } from './OpenAPI';
|
||||||
import { Paragraph } from './Paragraph';
|
import { Paragraph } from './Paragraph';
|
||||||
import { Quote } from './Quote';
|
import { Quote } from './Quote';
|
||||||
import { ReusableContent } from './ReusableContent';
|
import { ReusableContent } from './ReusableContent';
|
||||||
@@ -85,6 +85,8 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
|
|||||||
return <OpenAPIOperation {...props} block={block} />;
|
return <OpenAPIOperation {...props} block={block} />;
|
||||||
case 'openapi-schemas':
|
case 'openapi-schemas':
|
||||||
return <OpenAPISchemas {...props} block={block} />;
|
return <OpenAPISchemas {...props} block={block} />;
|
||||||
|
case 'openapi-webhook':
|
||||||
|
return <OpenAPIWebhook {...props} block={block} />;
|
||||||
case 'embed':
|
case 'embed':
|
||||||
return <Embed {...props} block={block} />;
|
return <Embed {...props} block={block} />;
|
||||||
case 'blockquote':
|
case 'blockquote':
|
||||||
@@ -159,6 +161,7 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
|
|||||||
case 'swagger':
|
case 'swagger':
|
||||||
case 'openapi-operation':
|
case 'openapi-operation':
|
||||||
case 'openapi-schemas':
|
case 'openapi-schemas':
|
||||||
|
case 'openapi-webhook':
|
||||||
case 'math':
|
case 'math':
|
||||||
case 'divider':
|
case 'divider':
|
||||||
case 'content-ref':
|
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 './scalar.css';
|
||||||
import './style.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.
|
* Get the OpenAPI context to render a block.
|
||||||
*/
|
*/
|
||||||
export function getOpenAPIContext(args: {
|
export function getOpenAPIContext(args: {
|
||||||
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>;
|
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
|
||||||
specUrl: string;
|
specUrl: string;
|
||||||
}): OpenAPIContext {
|
}): OpenAPIContext {
|
||||||
const { props, specUrl } = args;
|
const { props, specUrl } = args;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './OpenAPIOperation';
|
export * from './OpenAPIOperation';
|
||||||
export * from './OpenAPISchemas';
|
export * from './OpenAPISchemas';
|
||||||
|
export * from './OpenAPIWebhook';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* Layout Components */
|
/* Layout Components */
|
||||||
.openapi-operation,
|
.openapi-operation,
|
||||||
.openapi-schemas {
|
.openapi-schemas,
|
||||||
|
.openapi-webhook {
|
||||||
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
|
@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 {
|
import type {
|
||||||
AnyOpenAPIOperationsBlock,
|
AnyOpenAPIOperationsBlock,
|
||||||
OpenAPISchemasBlock,
|
OpenAPISchemasBlock,
|
||||||
|
OpenAPIWebhookBlock,
|
||||||
ResolveOpenAPIBlockArgs,
|
ResolveOpenAPIBlockArgs,
|
||||||
} from '@/lib/openapi/types';
|
} from '@/lib/openapi/types';
|
||||||
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
|
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
|
||||||
@@ -16,7 +17,7 @@ import { isV2 } from '../v2';
|
|||||||
import { enrichFilesystem } from './enrich';
|
import { enrichFilesystem } from './enrich';
|
||||||
import type { FetchOpenAPIFilesystemResult } from './types';
|
import type { FetchOpenAPIFilesystemResult } from './types';
|
||||||
|
|
||||||
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock;
|
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch OpenAPI block.
|
* 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,
|
DocumentBlockOpenAPI,
|
||||||
DocumentBlockOpenAPIOperation,
|
DocumentBlockOpenAPIOperation,
|
||||||
DocumentBlockOpenAPISchemas,
|
DocumentBlockOpenAPISchemas,
|
||||||
|
DocumentBlockOpenAPIWebhook,
|
||||||
} from '@gitbook/api';
|
} from '@gitbook/api';
|
||||||
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
|
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
|
||||||
import type { GitBookAnyContext } from '@v2/lib/context';
|
import type { GitBookAnyContext } from '@v2/lib/context';
|
||||||
@@ -16,6 +17,11 @@ export type AnyOpenAPIOperationsBlock = DocumentBlockOpenAPI | DocumentBlockOpen
|
|||||||
*/
|
*/
|
||||||
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
|
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for OpenAPI Webhook block
|
||||||
|
*/
|
||||||
|
export type OpenAPIWebhookBlock = DocumentBlockOpenAPIWebhook;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments for resolving OpenAPI block.
|
* Arguments for resolving OpenAPI block.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContext;
|
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 code samples are disabled at operation level, we don't display the code samples.
|
||||||
if (data.operation['x-codeSamples'] === false) {
|
if (data.operation['x-codeSamples'] === false) {
|
||||||
@@ -43,7 +43,9 @@ export function OpenAPICodeSample(props: {
|
|||||||
return null;
|
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}
|
method={data.method}
|
||||||
path={data.path}
|
path={data.path}
|
||||||
renderers={renderers}
|
renderers={renderers}
|
||||||
|
blockKey={context.blockKey}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
footer: (
|
footer: (
|
||||||
@@ -207,6 +210,7 @@ function OpenAPICodeSampleFooter(props: {
|
|||||||
path={data.path}
|
path={data.path}
|
||||||
renderers={renderers}
|
renderers={renderers}
|
||||||
selectIcon={context.icons.chevronDown}
|
selectIcon={context.icons.chevronDown}
|
||||||
|
blockKey={context.blockKey}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
|
|||||||
@@ -2,18 +2,20 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
||||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||||
|
import { createStateKey } from './utils';
|
||||||
|
|
||||||
export function OpenAPIMediaTypeExamplesSelector(props: {
|
export function OpenAPIMediaTypeExamplesSelector(props: {
|
||||||
method: string;
|
method: string;
|
||||||
path: string;
|
path: string;
|
||||||
renderers: MediaTypeRenderer[];
|
renderers: MediaTypeRenderer[];
|
||||||
selectIcon?: React.ReactNode;
|
selectIcon?: React.ReactNode;
|
||||||
|
blockKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const { method, path, renderers, selectIcon } = props;
|
const { method, path, renderers, selectIcon, blockKey } = props;
|
||||||
if (!renderers[0]) {
|
if (!renderers[0]) {
|
||||||
throw new Error('No renderers provided');
|
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 state = useSelectState(stateKey, renderers[0].mediaType);
|
||||||
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
|
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
|
||||||
|
|
||||||
@@ -102,13 +104,17 @@ export function OpenAPIMediaTypeExamplesBody(props: {
|
|||||||
method: string;
|
method: string;
|
||||||
path: string;
|
path: string;
|
||||||
renderers: MediaTypeRenderer[];
|
renderers: MediaTypeRenderer[];
|
||||||
|
blockKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const { renderers, method, path } = props;
|
const { renderers, method, path, blockKey } = props;
|
||||||
if (!renderers[0]) {
|
if (!renderers[0]) {
|
||||||
throw new Error('No renderers provided');
|
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];
|
const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
|
||||||
if (selected.examples.length === 0) {
|
if (selected.examples.length === 0) {
|
||||||
return selected.element;
|
return selected.element;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { generateSchemaExample } from './generateSchemaExample';
|
|
||||||
import { json2xml } from './json2xml';
|
import { json2xml } from './json2xml';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import type { OpenAPIContext } from './types';
|
import type { OpenAPIContext } from './types';
|
||||||
import { checkIsReference } from './utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an example.
|
* Display an example.
|
||||||
@@ -51,79 +49,3 @@ export function OpenAPIEmptyExample() {
|
|||||||
</pre>
|
</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 { OpenAPIEmptyExample } from './OpenAPIExample';
|
||||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||||
import { StaticSection } from './StaticSection';
|
import { StaticSection } from './StaticSection';
|
||||||
import { createStateKey } from './utils';
|
|
||||||
|
|
||||||
type OpenAPIResponseMediaTypeItem = OpenAPISelectItem & {
|
type OpenAPIMediaTypeItem = OpenAPISelectItem & {
|
||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
examples?: OpenAPIResponseMediaTypeItem[];
|
examples?: OpenAPIMediaTypeItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the state of the response examples select.
|
* Get the state of the response examples select.
|
||||||
*/
|
*/
|
||||||
export function useResponseMediaTypesState(
|
export function useMediaTypesState(stateKey: string | undefined, initialKey: Key = 'default') {
|
||||||
blockKey: string | undefined,
|
return useSelectState(stateKey, initialKey);
|
||||||
initialKey: Key = 'default'
|
|
||||||
) {
|
|
||||||
return useSelectState(getResponseMediaTypeStateKey(blockKey), initialKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useResponseMediaTypeExamplesState(
|
function useMediaTypeExamplesState(stateKey: string | undefined, initialKey: Key = 'default') {
|
||||||
blockKey: string | undefined,
|
return useSelectState(stateKey, initialKey);
|
||||||
initialKey: Key = 'default'
|
|
||||||
) {
|
|
||||||
return useSelectState(getResponseMediaTypeExamplesStateKey(blockKey), initialKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OpenAPIResponseMediaTypeContent(props: {
|
export function OpenAPIMediaTypeContent(props: {
|
||||||
items: OpenAPIResponseMediaTypeItem[];
|
items: OpenAPIMediaTypeItem[];
|
||||||
blockKey?: string;
|
|
||||||
selectIcon?: React.ReactNode;
|
selectIcon?: React.ReactNode;
|
||||||
|
stateKey: string;
|
||||||
}) {
|
}) {
|
||||||
const { blockKey, items, selectIcon } = props;
|
const { stateKey, items, selectIcon } = props;
|
||||||
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
|
const state = useMediaTypesState(stateKey, items[0]?.key);
|
||||||
|
|
||||||
const examples = items.find((item) => item.key === state.key)?.examples ?? [];
|
const examples = items.find((item) => item.key === state.key)?.examples ?? [];
|
||||||
|
|
||||||
@@ -45,28 +38,28 @@ export function OpenAPIResponseMediaTypeContent(props: {
|
|||||||
<StaticSection
|
<StaticSection
|
||||||
footer={
|
footer={
|
||||||
items.length > 1 || examples.length > 1 ? (
|
items.length > 1 || examples.length > 1 ? (
|
||||||
<OpenAPIResponseMediaTypeFooter
|
<OpenAPIMediaTypeFooter
|
||||||
blockKey={blockKey}
|
|
||||||
items={items}
|
items={items}
|
||||||
examples={examples}
|
examples={examples}
|
||||||
selectIcon={selectIcon}
|
selectIcon={selectIcon}
|
||||||
|
stateKey={stateKey}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
className="openapi-response-media-types-examples"
|
className="openapi-response-media-types-examples"
|
||||||
>
|
>
|
||||||
<OpenAPIResponseMediaTypeBody blockKey={blockKey} items={items} examples={examples} />
|
<OpenAPIMediaTypeBody stateKey={stateKey} items={items} examples={examples} />
|
||||||
</StaticSection>
|
</StaticSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenAPIResponseMediaTypeFooter(props: {
|
function OpenAPIMediaTypeFooter(props: {
|
||||||
items: OpenAPIResponseMediaTypeItem[];
|
items: OpenAPIMediaTypeItem[];
|
||||||
examples?: OpenAPIResponseMediaTypeItem[];
|
examples?: OpenAPIMediaTypeItem[];
|
||||||
blockKey?: string;
|
|
||||||
selectIcon?: React.ReactNode;
|
selectIcon?: React.ReactNode;
|
||||||
|
stateKey: string;
|
||||||
}) {
|
}) {
|
||||||
const { items, examples, blockKey, selectIcon } = props;
|
const { items, examples, stateKey, selectIcon } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -74,7 +67,7 @@ function OpenAPIResponseMediaTypeFooter(props: {
|
|||||||
<OpenAPISelect
|
<OpenAPISelect
|
||||||
icon={selectIcon}
|
icon={selectIcon}
|
||||||
items={items}
|
items={items}
|
||||||
stateKey={getResponseMediaTypeStateKey(blockKey)}
|
stateKey={stateKey}
|
||||||
placement="bottom start"
|
placement="bottom start"
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
@@ -89,7 +82,7 @@ function OpenAPIResponseMediaTypeFooter(props: {
|
|||||||
<OpenAPISelect
|
<OpenAPISelect
|
||||||
icon={selectIcon}
|
icon={selectIcon}
|
||||||
items={examples}
|
items={examples}
|
||||||
stateKey={getResponseMediaTypeExamplesStateKey(blockKey)}
|
stateKey={`${stateKey}-examples`}
|
||||||
placement="bottom start"
|
placement="bottom start"
|
||||||
>
|
>
|
||||||
{examples.map((example) => (
|
{examples.map((example) => (
|
||||||
@@ -103,18 +96,18 @@ function OpenAPIResponseMediaTypeFooter(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenAPIResponseMediaTypeBody(props: {
|
function OpenAPIMediaTypeBody(props: {
|
||||||
items: OpenAPIResponseMediaTypeItem[];
|
items: OpenAPIMediaTypeItem[];
|
||||||
examples?: OpenAPIResponseMediaTypeItem[];
|
examples?: OpenAPIMediaTypeItem[];
|
||||||
blockKey?: string;
|
stateKey: string;
|
||||||
}) {
|
}) {
|
||||||
const { blockKey, items, examples } = props;
|
const { stateKey, items, examples } = props;
|
||||||
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
|
const state = useMediaTypesState(stateKey, items[0]?.key);
|
||||||
|
|
||||||
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
|
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
|
||||||
|
|
||||||
const exampleState = useResponseMediaTypeExamplesState(
|
const exampleState = useMediaTypeExamplesState(
|
||||||
blockKey,
|
`${stateKey}-examples`,
|
||||||
selectedItem?.examples?.[0]?.key
|
selectedItem?.examples?.[0]?.key
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -135,14 +128,3 @@ function OpenAPIResponseMediaTypeBody(props: {
|
|||||||
|
|
||||||
return selectedItem.body;
|
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 clsx from 'clsx';
|
||||||
|
|
||||||
import type {
|
|
||||||
OpenAPICustomOperationProperties,
|
|
||||||
OpenAPIStability,
|
|
||||||
OpenAPIV3,
|
|
||||||
} from '@gitbook/openapi-parser';
|
|
||||||
import { Markdown } from './Markdown';
|
|
||||||
import { OpenAPICodeSample } from './OpenAPICodeSample';
|
import { OpenAPICodeSample } from './OpenAPICodeSample';
|
||||||
import { OpenAPIPath } from './OpenAPIPath';
|
|
||||||
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
||||||
import { OpenAPISpec } from './OpenAPISpec';
|
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
|
||||||
import { getOpenAPIClientContext } from './context';
|
import { OpenAPISummary } from './common/OpenAPISummary';
|
||||||
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||||
import { resolveDescription } from './utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an interactive OpenAPI operation.
|
* Display an interactive OpenAPI operation.
|
||||||
@@ -23,46 +14,12 @@ export function OpenAPIOperation(props: {
|
|||||||
context: OpenAPIContext;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { className, data, context } = props;
|
const { className, data, context } = props;
|
||||||
const { operation } = data;
|
|
||||||
|
|
||||||
const clientContext = getOpenAPIClientContext(context);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('openapi-operation', className)}>
|
<div className={clsx('openapi-operation', className)}>
|
||||||
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
|
<OpenAPISummary data={data} context={context} />
|
||||||
{(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>
|
|
||||||
<div className="openapi-columns">
|
<div className="openapi-columns">
|
||||||
<div className="openapi-column-spec">
|
<OpenAPIColumnSpec data={data} context={context} />
|
||||||
{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>
|
|
||||||
<div className="openapi-column-preview">
|
<div className="openapi-column-preview">
|
||||||
<div className="openapi-column-preview-body">
|
<div className="openapi-column-preview-body">
|
||||||
<OpenAPICodeSample {...props} />
|
<OpenAPICodeSample {...props} />
|
||||||
@@ -73,52 +30,3 @@ export function OpenAPIOperation(props: {
|
|||||||
</div>
|
</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 type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
|
import { OpenAPIRootSchema } from './OpenAPISchemaServer';
|
||||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
import type { OpenAPIClientContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||||
import { checkIsReference } from './utils';
|
import { checkIsReference, createStateKey } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an interactive request body.
|
* Display an interactive request body.
|
||||||
@@ -10,20 +10,21 @@ import { checkIsReference } from './utils';
|
|||||||
export function OpenAPIRequestBody(props: {
|
export function OpenAPIRequestBody(props: {
|
||||||
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
|
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
|
||||||
context: OpenAPIClientContext;
|
context: OpenAPIClientContext;
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||||
}) {
|
}) {
|
||||||
const { requestBody, context, data } = props;
|
const { requestBody, context, data } = props;
|
||||||
const { method, path } = data;
|
|
||||||
|
|
||||||
if (checkIsReference(requestBody)) {
|
if (checkIsReference(requestBody)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header = 'name' in data ? 'Payload' : 'Body';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
header="Body"
|
header={header}
|
||||||
className="openapi-requestbody"
|
className="openapi-requestbody"
|
||||||
stateKey={`media-type-${method}-${path}`}
|
stateKey={createStateKey('request-body-media-type', context.blockKey)}
|
||||||
selectIcon={context.icons.chevronDown}
|
selectIcon={context.icons.chevronDown}
|
||||||
tabs={Object.entries(requestBody.content ?? {}).map(
|
tabs={Object.entries(requestBody.content ?? {}).map(
|
||||||
([contentType, mediaTypeObject]) => {
|
([contentType, mediaTypeObject]) => {
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { Markdown } from './Markdown';
|
import { Markdown } from './Markdown';
|
||||||
import {
|
import { OpenAPIEmptyExample, OpenAPIExample } from './OpenAPIExample';
|
||||||
OpenAPIEmptyExample,
|
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
|
||||||
OpenAPIExample,
|
|
||||||
getExampleFromReference,
|
|
||||||
getExamplesFromMediaTypeObject,
|
|
||||||
} from './OpenAPIExample';
|
|
||||||
import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
|
import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
|
||||||
import { OpenAPIResponseMediaTypeContent } from './OpenAPIResponseMediaType';
|
import type { OpenAPIContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||||
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
import { getExampleFromReference, getExamples } from './util/example';
|
||||||
import { getStatusCodeDefaultLabel } from './utils';
|
import { createStateKey, getStatusCodeDefaultLabel } from './utils';
|
||||||
import { checkIsReference, resolveDescription } from './utils';
|
import { checkIsReference, resolveDescription } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an example of the response content.
|
* Display an example of the response content.
|
||||||
*/
|
*/
|
||||||
export function OpenAPIResponseExample(props: {
|
export function OpenAPIResponseExample(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData | OpenAPIWebhookData;
|
||||||
context: OpenAPIContext;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data, context } = props;
|
const { data, context } = props;
|
||||||
@@ -127,45 +123,10 @@ function OpenAPIResponse(props: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPIResponseMediaTypeContent
|
<OpenAPIMediaTypeContent
|
||||||
selectIcon={context.icons.chevronDown}
|
selectIcon={context.icons.chevronDown}
|
||||||
blockKey={context.blockKey}
|
stateKey={createStateKey('response-media-types', context.blockKey)}
|
||||||
items={tabs}
|
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 { OpenAPISchemaProperties } from './OpenAPISchemaServer';
|
||||||
import { OpenAPISecurities } from './OpenAPISecurities';
|
import { OpenAPISecurities } from './OpenAPISecurities';
|
||||||
import { StaticSection } from './StaticSection';
|
import { StaticSection } from './StaticSection';
|
||||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
import type { OpenAPIClientContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
|
||||||
import { parameterToProperty } from './utils';
|
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 { data, context } = props;
|
||||||
|
|
||||||
const { operation, securities } = data;
|
const { operation } = data;
|
||||||
|
|
||||||
const parameters = operation.parameters ?? [];
|
const parameters = operation.parameters ?? [];
|
||||||
const parameterGroups = groupParameters(parameters);
|
const parameterGroups = groupParameters(parameters);
|
||||||
|
|
||||||
|
const securities = 'securities' in data ? data.securities : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{securities.length > 0 ? (
|
{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 './schemas';
|
||||||
export * from './OpenAPIOperation';
|
export * from './OpenAPIOperation';
|
||||||
|
export * from './OpenAPIWebhook';
|
||||||
export * from './OpenAPIOperationContext';
|
export * from './OpenAPIOperationContext';
|
||||||
export * from './resolveOpenAPIOperation';
|
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 type { OpenAPISchema } from '@gitbook/openapi-parser';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { OpenAPIDisclosure } from '../OpenAPIDisclosure';
|
import { OpenAPIDisclosure } from '../OpenAPIDisclosure';
|
||||||
import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample';
|
import { OpenAPIExample } from '../OpenAPIExample';
|
||||||
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
|
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
|
||||||
import { Section, SectionBody, StaticSection } from '../StaticSection';
|
import { Section, SectionBody, StaticSection } from '../StaticSection';
|
||||||
import { getOpenAPIClientContext } from '../context';
|
import { getOpenAPIClientContext } from '../context';
|
||||||
import type { OpenAPIContext } from '../types';
|
import type { OpenAPIContext } from '../types';
|
||||||
|
import { getExampleFromSchema } from '../util/example';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenAPI Schemas component.
|
* OpenAPI Schemas component.
|
||||||
|
|||||||
@@ -72,3 +72,14 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
|||||||
/** Securities that should be used for this operation */
|
/** Securities that should be used for this operation */
|
||||||
securities: [string, OpenAPISecurityWithRequired][];
|
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