Skip to content

Commit f5cba13

Browse files
authored
[Docs Site] Add APIRequest and CURL components (#20469)
* [Docs Site] Add APIRequest and CURL components * permissions dropdown * use summary as title * use long flags * remove demo
1 parent eec84fb commit f5cba13

File tree

12 files changed

+429
-35
lines changed

12 files changed

+429
-35
lines changed

astro.config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ export default defineConfig({
149149
tailwind({
150150
applyBaseStyles: false,
151151
}),
152-
liveCode({
153-
layout: "~/components/live-code/Layout.astro",
154-
}),
152+
liveCode({}),
155153
icon(),
156154
sitemap({
157155
filter(page) {

package-lock.json

Lines changed: 116 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"devDependencies": {
2828
"@actions/core": "1.11.1",
2929
"@actions/github": "6.0.0",
30+
"@apidevtools/swagger-parser": "10.1.1",
3031
"@astrojs/check": "0.9.4",
3132
"@astrojs/react": "4.2.0",
3233
"@astrojs/rss": "4.0.11",
@@ -77,6 +78,7 @@
7778
"mdast-util-mdx-expression": "2.0.1",
7879
"mermaid": "11.4.1",
7980
"node-html-parser": "7.0.1",
81+
"openapi-types": "12.1.3",
8082
"parse-duration": "2.1.3",
8183
"prettier": "3.5.2",
8284
"prettier-plugin-astro": "0.14.1",

src/components/APIRequest.astro

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
import { z } from "astro:schema";
3+
import { getProperty } from "dot-prop";
4+
import { getSchema } from "~/util/api.ts";
5+
import type { OpenAPIV3 } from "openapi-types";
6+
import CURL from "./CURL.astro";
7+
import Details from "./Details.astro";
8+
9+
type Props = z.input<typeof props>;
10+
11+
const props = z.object({
12+
path: z.string(),
13+
method: z.enum(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"]),
14+
json: z.record(z.string(), z.any()).default({}),
15+
});
16+
17+
const { path, method, json } = props.parse(Astro.props);
18+
19+
const schema = await getSchema();
20+
21+
const operation = getProperty(
22+
schema,
23+
`paths.${path}.${method.toLowerCase()}`,
24+
) as unknown as
25+
| OpenAPIV3.OperationObject<{
26+
"x-api-token-group"?: string[];
27+
}>
28+
| undefined;
29+
30+
if (!operation) {
31+
throw new Error(
32+
`[APIRequest] Operation ${method} ${path} not found in schema.`,
33+
);
34+
}
35+
36+
const url = new URL(path, "https://api.cloudflare.com/client/v4");
37+
const headers: Record<string, string> = {};
38+
39+
const segments = url.pathname.split("/").filter(Boolean);
40+
for (const segment of segments) {
41+
const decoded = decodeURIComponent(segment);
42+
43+
if (decoded.startsWith("{") && decoded.endsWith("}")) {
44+
const placeholder = "$" + decoded.slice(1, -1).toUpperCase();
45+
46+
url.pathname = url.pathname.replace(segment, placeholder);
47+
}
48+
}
49+
50+
const security = operation.security as
51+
| OpenAPIV3.SecurityRequirementObject[]
52+
| undefined;
53+
54+
if (security) {
55+
const keys = security.flatMap((requirement) => Object.keys(requirement));
56+
57+
if (keys.includes("api_token")) {
58+
headers["Authorization"] = `Bearer $CLOUDFLARE_API_TOKEN`;
59+
} else if (keys.includes("api_key")) {
60+
headers["X-Auth-Email"] = "$CLOUDFLARE_EMAIL";
61+
headers["X-Auth-Key"] = "$CLOUDFLARE_API_KEY";
62+
}
63+
}
64+
65+
const requestBody = operation?.requestBody as
66+
| OpenAPIV3.RequestBodyObject
67+
| undefined;
68+
69+
const jsonSchema = requestBody?.content?.["application/json"]?.schema as
70+
| OpenAPIV3.SchemaObject
71+
| undefined;
72+
73+
if (!jsonSchema) {
74+
throw new Error(
75+
`[APIRequest] This component currently does not support operations that do not accept JSON bodies.`,
76+
);
77+
}
78+
79+
if (jsonSchema.required) {
80+
const providedProperties = Object.keys(json);
81+
const requiredProperties = jsonSchema.required;
82+
83+
const missingProperties = requiredProperties.filter(
84+
(property) => !providedProperties.includes(property),
85+
);
86+
87+
for (const property of missingProperties) {
88+
const defaultValue =
89+
(jsonSchema.properties?.[property] as OpenAPIV3.SchemaObject).default ??
90+
property;
91+
92+
json[property] = defaultValue;
93+
}
94+
}
95+
96+
const tokenGroups = operation["x-api-token-group"];
97+
---
98+
99+
{
100+
tokenGroups && (
101+
<Details header="Required API token permissions">
102+
<span>
103+
At least one of the following{" "}
104+
<a href="/fundamentals/api/reference/permissions/">token permissions</a>{" "}
105+
is required:
106+
</span>
107+
<ul>
108+
{tokenGroups.map((group) => (
109+
<li>{group}</li>
110+
))}
111+
</ul>
112+
</Details>
113+
)
114+
}
115+
116+
<CURL
117+
url={url.toString()}
118+
method={method}
119+
headers={headers}
120+
json={json}
121+
code={{
122+
title: operation.summary,
123+
}}
124+
/>

src/components/CURL.astro

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
import { z } from "astro:schema";
3+
import type { ComponentProps } from "astro/types";
4+
import { Code } from "@astrojs/starlight/components";
5+
6+
type Props = z.input<typeof props>;
7+
8+
const props = z.object({
9+
method: z
10+
.enum(["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"])
11+
.default("GET"),
12+
url: z.string().url(),
13+
headers: z.record(z.string(), z.string()).default({}),
14+
json: z.record(z.string(), z.any()).optional(),
15+
code: z
16+
.custom<Omit<ComponentProps<typeof Code>, "code" | "lang">>()
17+
.optional(),
18+
});
19+
20+
const { method, url, headers, json, code } = props.parse(Astro.props);
21+
22+
const lines = [`curl ${url}`, `\t--request ${method}`];
23+
24+
if (headers) {
25+
for (const [key, value] of Object.entries(headers)) {
26+
lines.push(`\t--header "${key}: ${value}"`);
27+
}
28+
}
29+
30+
if (json) {
31+
const jsonLines = JSON.stringify(json, null, "\t\t").split("\n");
32+
jsonLines[jsonLines.length - 1] = "\t" + jsonLines[jsonLines.length - 1];
33+
34+
lines.push(`\t--json '${jsonLines.join("\n")}'`);
35+
}
36+
---
37+
38+
<Code {...code} lang="bash" code={lines.join(" \\\n")} />

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ export { PackageManagers } from "starlight-package-managers";
66
export { Icon as AstroIcon } from "astro-icon/components";
77
// Custom components
88
export { default as AnchorHeading } from "./AnchorHeading.astro";
9+
export { default as APIRequest } from "./APIRequest.astro";
910
export { default as AvailableNotifications } from "./AvailableNotifications.astro";
1011
export { default as CompatibilityFlag } from "./CompatibilityFlag.astro";
1112
export { default as CompatibilityFlags } from "./CompatibilityFlags.astro";
13+
export { default as CURL } from "./CURL.astro";
1214
export { default as Description } from "./Description.astro";
1315
export { default as Details } from "./Details.astro";
1416
export { default as DirectoryListing } from "./DirectoryListing.astro";

src/components/live-code/Layout.astro

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)