Skip to content

Commit 7af51b1

Browse files
authored
[Enhancement] Introduce support for webhooks extension (#370)
* Update mustache template to get title and sidebar label from frontmatter * Add styles for event method * Add x-webhooks to OpenApiObject type * Include x-webhooks in api items * Remove webhook badge and revert mustache template
1 parent 7ed6887 commit 7af51b1

4 files changed

Lines changed: 124 additions & 0 deletions

File tree

demo/src/css/custom.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ a:any-link:hover {
9494
background-color: var(--ifm-color-secondary-darkest);
9595
}
9696

97+
.event > .menu__link::before {
98+
content: "event";
99+
background-color: var(--ifm-color-secondary-darkest);
100+
}
101+
97102
/* GitHub Header Link */
98103
.header-github-link:hover {
99104
opacity: 0.6;

packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,122 @@ function createItems(
239239
}
240240
}
241241

242+
// Gather x-webhooks endpoints
243+
for (let [path, pathObject] of Object.entries(
244+
openapiData["x-webhooks"] ?? {}
245+
)) {
246+
path = "webhook";
247+
const { $ref, description, parameters, servers, summary, ...rest } =
248+
pathObject;
249+
for (let [method, operationObject] of Object.entries({ ...rest })) {
250+
method = "event";
251+
const title =
252+
operationObject.summary ??
253+
operationObject.operationId ??
254+
"Missing summary";
255+
if (operationObject.description === undefined) {
256+
operationObject.description =
257+
operationObject.summary ?? operationObject.operationId ?? "";
258+
}
259+
260+
const baseId = operationObject.operationId
261+
? kebabCase(operationObject.operationId)
262+
: kebabCase(operationObject.summary);
263+
264+
const servers =
265+
operationObject.servers ?? pathObject.servers ?? openapiData.servers;
266+
267+
const security = operationObject.security ?? openapiData.security;
268+
269+
// Add security schemes so we know how to handle security.
270+
const securitySchemes = openapiData.components?.securitySchemes;
271+
272+
// Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
273+
if (securitySchemes) {
274+
for (let securityScheme of Object.values(securitySchemes)) {
275+
if (securityScheme.type === "http") {
276+
securityScheme.scheme = securityScheme.scheme.toLowerCase();
277+
}
278+
}
279+
}
280+
281+
let jsonRequestBodyExample;
282+
const body = operationObject.requestBody?.content?.["application/json"];
283+
if (body?.schema) {
284+
jsonRequestBodyExample = sampleRequestFromSchema(body.schema);
285+
}
286+
287+
// Handle vendor JSON media types
288+
const bodyContent = operationObject.requestBody?.content;
289+
if (bodyContent) {
290+
const firstBodyContentKey = Object.keys(bodyContent)[0];
291+
if (firstBodyContentKey.endsWith("+json")) {
292+
const firstBody = bodyContent[firstBodyContentKey];
293+
if (firstBody?.schema) {
294+
jsonRequestBodyExample = sampleRequestFromSchema(firstBody.schema);
295+
}
296+
}
297+
}
298+
299+
// TODO: Don't include summary temporarilly
300+
const { summary, ...defaults } = operationObject;
301+
302+
// Merge common parameters with operation parameters
303+
// Operation params take precendence over common params
304+
if (parameters !== undefined) {
305+
if (operationObject.parameters !== undefined) {
306+
defaults.parameters = unionBy(
307+
operationObject.parameters,
308+
parameters,
309+
"name"
310+
);
311+
} else {
312+
defaults.parameters = parameters;
313+
}
314+
}
315+
316+
const opDescription = operationObject.description;
317+
let splitDescription: any;
318+
if (opDescription) {
319+
splitDescription = opDescription.match(/[^\r\n]+/g);
320+
}
321+
322+
const apiPage: PartialPage<ApiPageMetadata> = {
323+
type: "api",
324+
id: baseId,
325+
infoId: infoId ?? "",
326+
unversionedId: baseId,
327+
title: title ? title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'") : "",
328+
description: operationObject.description
329+
? operationObject.description.replace(
330+
/((?:^|[^\\])(?:\\{2})*)"/g,
331+
"$1'"
332+
)
333+
: "",
334+
frontMatter: {
335+
description: splitDescription
336+
? splitDescription[0]
337+
.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
338+
.replace(/\s+$/, "")
339+
: "",
340+
...(options?.proxy && { proxy: options.proxy }),
341+
},
342+
api: {
343+
...defaults,
344+
tags: operationObject.tags,
345+
method,
346+
path,
347+
servers,
348+
security,
349+
securitySchemes,
350+
jsonRequestBodyExample,
351+
info: openapiData.info,
352+
},
353+
};
354+
items.push(apiPage);
355+
}
356+
}
357+
242358
if (sidebarOptions?.categoryLinkSource === "tag") {
243359
// Get global tags
244360
const tags: TagObject[] = openapiData.tags ?? [];

packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface OpenApiObject {
2121
tags?: TagObject[];
2222
externalDocs?: ExternalDocumentationObject;
2323
swagger?: string;
24+
"x-webhooks"?: PathsObject;
2425
}
2526

2627
export interface OpenApiObjectWithRef {

packages/docusaurus-theme-openapi-docs/src/theme/ApiDemoPanel/MethodEndpoint/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ function colorForMethod(method: string) {
2525
return "warning";
2626
case "head":
2727
return "secondary";
28+
case "event":
29+
return "secondary";
2830
default:
2931
return undefined;
3032
}

0 commit comments

Comments
 (0)