@gitbook/react-openapi 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4771c78: Fixed scalar api client routing
8
+ - ff50ac2: Bump @scalar/api-client-react version
9
+ - 867481c: Support Integers in Response example
10
+ - 7ba67fd: bumped the scalar api client dependency
11
+ - a78c1ec: Bumped scalar api client pacakge
12
+
13
+ ## 0.7.0
14
+
15
+ ### Minor Changes
16
+
17
+ - cf3045a: Add Python support in Code Samples
18
+ - 4247361: Add required query parameters to the code sample
19
+ - aa8c49e: Display pattern if available in parmas in OpenAPI block
20
+ - e914903: Synchronize response and response example tabs
21
+ - 4cbcc5b: Rollback of scalar modal while fixing perf issue
22
+
23
+ ### Patch Changes
24
+
25
+ - 51fa3ab: Adds content-visibility css property to OpenAPI Operation for better render performance
26
+ - f89b31c: Upgrade the scalar api client package
27
+ - 094e9cd: bump: scalar from 1.0.5 to 1.0.7
28
+ - 237b703: Fix crash when `example` is undefined for a response
29
+ - 51955da: Adds tabs to Response Example section e.g. for status code examples
30
+ - a679e72: Render mandatory headers in code sample
31
+ - c079c3c: Update Scalar client to latest version
32
+
3
33
  ## 0.6.0
4
34
 
5
35
  ### Minor Changes
@@ -29,5 +29,7 @@ export declare function InteractiveSection(props: {
29
29
  children?: React.ReactNode;
30
30
  /** Children to display within the container */
31
31
  overlay?: React.ReactNode;
32
+ /** An optional key referencing a value in global state */
33
+ stateKey?: string;
32
34
  }): React.JSX.Element;
33
35
  export {};
@@ -1,15 +1,24 @@
1
1
  'use client';
2
2
  import classNames from 'classnames';
3
3
  import React from 'react';
4
+ import { atom, useRecoilState } from 'recoil';
5
+ const syncedTabsAtom = atom({
6
+ key: 'syncedTabState',
7
+ default: {},
8
+ });
4
9
  /**
5
10
  * To optimize rendering, most of the components are server-components,
6
11
  * and the interactiveness is mainly handled by a few key components like this one.
7
12
  */
8
13
  export function InteractiveSection(props) {
9
- const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, children, overlay, toggleOpenIcon = '▶', toggleCloseIcon = '▼', } = props;
14
+ const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, children, overlay, toggleOpenIcon = '▶', toggleCloseIcon = '▼', stateKey, } = props;
15
+ const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
16
+ const tabFromState = stateKey && stateKey in syncedTabs
17
+ ? tabs.find((tab) => tab.key === syncedTabs[stateKey])
18
+ : undefined;
10
19
  const [opened, setOpened] = React.useState(defaultOpened);
11
- const [selectedTabKey, setSelectedTab] = React.useState(defaultTab);
12
- const selectedTab = tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
20
+ const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
21
+ const selectedTab = tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
13
22
  return (React.createElement("div", { id: id, className: classNames('openapi-section', toggeable ? 'openapi-section-toggeable' : null, className, toggeable ? `${className}-${opened ? 'opened' : 'closed'}` : null) },
14
23
  React.createElement("div", { onClick: () => {
15
24
  if (toggeable) {
@@ -22,6 +31,12 @@ export function InteractiveSection(props) {
22
31
  } },
23
32
  tabs.length ? (React.createElement("select", { className: classNames('openapi-section-select', 'openapi-select', `${className}-tabs-select`), value: selectedTab.key, onChange: (event) => {
24
33
  setSelectedTab(event.target.value);
34
+ if (stateKey) {
35
+ setSyncedTabs((state) => ({
36
+ ...state,
37
+ [stateKey]: event.target.value,
38
+ }));
39
+ }
25
40
  setOpened(true);
26
41
  } }, tabs.map((tab) => (React.createElement("option", { key: tab.key, value: tab.key }, tab.label))))) : null,
27
42
  (children || selectedTab?.body) && toggeable ? (React.createElement("button", { className: classNames('openapi-section-toggle', `${className}-toggle`), onClick: () => setOpened(!opened) }, opened ? toggleCloseIcon : toggleOpenIcon)) : null)),
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { codeSampleGenerators } from './code-samples';
3
- import { generateMediaTypeExample } from './generateSchemaExample';
3
+ import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
4
4
  import { InteractiveSection } from './InteractiveSection';
5
5
  import { getServersURL } from './OpenAPIServerURL';
6
6
  import { ScalarApiButton } from './ScalarApiButton';
@@ -11,16 +11,44 @@ import { noReference } from './utils';
11
11
  */
12
12
  export function OpenAPICodeSample(props) {
13
13
  const { data, context } = props;
14
+ const searchParams = new URLSearchParams();
15
+ const headersObject = {};
16
+ data.operation.parameters?.forEach((rawParam) => {
17
+ const param = noReference(rawParam);
18
+ if (!param) {
19
+ return;
20
+ }
21
+ if (param.in === 'header' && param.required) {
22
+ const example = param.schema
23
+ ? generateSchemaExample(noReference(param.schema))
24
+ : undefined;
25
+ if (example !== undefined) {
26
+ headersObject[param.name] =
27
+ typeof example !== 'string' ? JSON.stringify(example) : example;
28
+ }
29
+ }
30
+ else if (param.in === 'query' && param.required) {
31
+ const example = param.schema
32
+ ? generateSchemaExample(noReference(param.schema))
33
+ : undefined;
34
+ if (example !== undefined) {
35
+ searchParams.append(param.name, String(Array.isArray(example) ? example[0] : example));
36
+ }
37
+ }
38
+ });
14
39
  const requestBody = noReference(data.operation.requestBody);
15
40
  const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;
16
41
  const input = {
17
- url: getServersURL(data.servers) + data.path,
42
+ url: getServersURL(data.servers) +
43
+ data.path +
44
+ (searchParams.size ? `?${searchParams.toString()}` : ''),
18
45
  method: data.method,
19
46
  body: requestBodyContent
20
47
  ? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
21
48
  : undefined,
22
49
  headers: {
23
50
  ...getSecurityHeaders(data.securities),
51
+ ...headersObject,
24
52
  ...(requestBodyContent
25
53
  ? {
26
54
  'Content-Type': requestBodyContent[0],
@@ -38,7 +66,13 @@ export function OpenAPICodeSample(props) {
38
66
  ['x-custom-examples', 'x-code-samples', 'x-codeSamples'].forEach((key) => {
39
67
  const customSamples = data.operation[key];
40
68
  if (customSamples && Array.isArray(customSamples)) {
41
- customCodeSamples = customSamples.map((sample) => ({
69
+ customCodeSamples = customSamples
70
+ .filter((sample) => {
71
+ return (typeof sample.label === 'string' &&
72
+ typeof sample.source === 'string' &&
73
+ typeof sample.lang === 'string');
74
+ })
75
+ .map((sample) => ({
42
76
  key: `redocly-${sample.lang}`,
43
77
  label: sample.label,
44
78
  body: React.createElement(context.CodeBlock, { code: sample.source, syntax: sample.lang }),
@@ -52,7 +86,7 @@ export function OpenAPICodeSample(props) {
52
86
  if (samples.length === 0) {
53
87
  return null;
54
88
  }
55
- return (React.createElement(InteractiveSection, { header: "Request", className: "openapi-codesample", tabs: samples, overlay: data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (React.createElement(ScalarApiButton, null)) }));
89
+ return (React.createElement(InteractiveSection, { header: "Request", className: "openapi-codesample", tabs: samples, overlay: data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (React.createElement(ScalarApiButton, { method: data.method, path: data.path })) }));
56
90
  }
57
91
  function getSecurityHeaders(securities) {
58
92
  const security = securities[0];
@@ -1,12 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import classNames from 'classnames';
3
+ import { ApiClientModalProvider } from '@scalar/api-client-react';
3
4
  import { toJSON } from './fetchOpenAPIOperation';
4
5
  import { Markdown } from './Markdown';
5
6
  import { OpenAPICodeSample } from './OpenAPICodeSample';
6
7
  import { OpenAPIResponseExample } from './OpenAPIResponseExample';
7
8
  import { OpenAPIServerURL } from './OpenAPIServerURL';
8
9
  import { OpenAPISpec } from './OpenAPISpec';
9
- import { ApiClientModalProvider } from '@scalar/api-client-react';
10
10
  /**
11
11
  * Display an interactive OpenAPI operation.
12
12
  */
@@ -15,13 +15,13 @@ export function OpenAPIOperation(props) {
15
15
  const { operation, servers, method, path } = data;
16
16
  const clientContext = {
17
17
  defaultInteractiveOpened: context.defaultInteractiveOpened,
18
- specUrl: context.specUrl,
19
18
  icons: context.icons,
19
+ blockKey: context.blockKey,
20
20
  };
21
21
  return (React.createElement(ApiClientModalProvider, { configuration: { spec: { url: context.specUrl } }, initialRequest: { path: data.path, method: data.method } },
22
22
  React.createElement("div", { className: classNames('openapi-operation', className) },
23
23
  React.createElement("div", { className: "openapi-intro" },
24
- React.createElement("h2", { className: "openapi-summary" }, operation.summary),
24
+ React.createElement("h2", { className: "openapi-summary", id: context.id }, operation.summary),
25
25
  operation.description ? (React.createElement(Markdown, { className: "openapi-description", source: operation.description })) : null,
26
26
  React.createElement("div", { className: "openapi-target" },
27
27
  React.createElement("span", { className: classNames('openapi-method', `openapi-method-${method.toLowerCase()}`) }, method.toUpperCase()),
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { InteractiveSection } from './InteractiveSection';
3
3
  import { generateSchemaExample } from './generateSchemaExample';
4
- import { noReference } from './utils';
4
+ import { createStateKey, noReference } from './utils';
5
5
  /**
6
6
  * Display an example of the response content.
7
7
  */
@@ -28,21 +28,27 @@ export function OpenAPIResponseExample(props) {
28
28
  }
29
29
  return Number(a) - Number(b);
30
30
  });
31
- // Take the first one
32
- const response = responses[0];
33
- if (!response) {
34
- return null;
35
- }
36
- const responseObject = noReference(response[1]);
37
- const schema = noReference((responseObject.content?.['application/json'] ??
38
- responseObject.content?.[Object.keys(responseObject.content)[0]])?.schema);
39
- if (!schema) {
40
- return null;
41
- }
42
- const example = generateSchemaExample(schema);
43
- if (example === undefined) {
31
+ const examples = responses
32
+ .map((response) => {
33
+ const responseObject = noReference(response[1]);
34
+ const schema = noReference((responseObject.content?.['application/json'] ??
35
+ responseObject.content?.[Object.keys(responseObject.content)[0]])?.schema);
36
+ if (!schema) {
37
+ return null;
38
+ }
39
+ const example = generateSchemaExample(schema);
40
+ if (example === undefined) {
41
+ return null;
42
+ }
43
+ return {
44
+ key: `${response[0]}`,
45
+ label: `${response[0]}`,
46
+ body: (React.createElement(context.CodeBlock, { code: typeof example === 'string' ? example : JSON.stringify(example, null, 2), syntax: "json" })),
47
+ };
48
+ })
49
+ .filter((val) => Boolean(val));
50
+ if (examples.length === 0) {
44
51
  return null;
45
52
  }
46
- return (React.createElement(InteractiveSection, { header: "Response", className: "openapi-response-example" },
47
- React.createElement(context.CodeBlock, { code: typeof example === 'string' ? example : JSON.stringify(example, null, 2), syntax: "json" })));
53
+ return (React.createElement(InteractiveSection, { stateKey: createStateKey('response', context.blockKey), header: "Response", className: "openapi-response-example", tabs: examples }));
48
54
  }
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import classNames from 'classnames';
3
- import { noReference } from './utils';
3
+ import { createStateKey, noReference } from './utils';
4
4
  import { OpenAPIResponse } from './OpenAPIResponse';
5
5
  import { InteractiveSection } from './InteractiveSection';
6
6
  /**
@@ -8,7 +8,7 @@ import { InteractiveSection } from './InteractiveSection';
8
8
  */
9
9
  export function OpenAPIResponses(props) {
10
10
  const { responses, context } = props;
11
- return (React.createElement(InteractiveSection, { header: "Response", className: classNames('openapi-responses'), tabs: Object.entries(responses).map(([statusCode, response]) => {
11
+ return (React.createElement(InteractiveSection, { stateKey: createStateKey('response', context.blockKey), header: "Response", className: classNames('openapi-responses'), tabs: Object.entries(responses).map(([statusCode, response]) => {
12
12
  return {
13
13
  key: statusCode,
14
14
  label: statusCode,
@@ -34,7 +34,10 @@ export function OpenAPISchemaProperty(props) {
34
34
  schema.description ? (React.createElement(Markdown, { source: schema.description, className: "openapi-schema-description" })) : null,
35
35
  shouldDisplayExample(schema) ? (React.createElement("span", { className: "openapi-schema-example" },
36
36
  "Example: ",
37
- React.createElement("code", null, JSON.stringify(schema.example)))) : null) }, (properties && properties.length > 0) ||
37
+ React.createElement("code", null, JSON.stringify(schema.example)))) : null,
38
+ schema.pattern ? (React.createElement("div", { className: "openapi-schema-pattern" },
39
+ "Pattern: ",
40
+ React.createElement("code", null, schema.pattern))) : null) }, (properties && properties.length > 0) ||
38
41
  (schema.enum && schema.enum.length > 0) ||
39
42
  parentCircularRef ? (React.createElement(React.Fragment, null,
40
43
  properties?.length ? (React.createElement(OpenAPISchemaProperties, { properties: properties, circularRefs: circularRefs, context: context })) : null,
@@ -2,4 +2,7 @@ import React from 'react';
2
2
  /**
3
3
  * Button which launches the Scalar API Client
4
4
  */
5
- export declare function ScalarApiButton(): React.JSX.Element;
5
+ export declare function ScalarApiButton({ method, path }: {
6
+ method: string;
7
+ path: string;
8
+ }): React.JSX.Element;
@@ -4,10 +4,10 @@ import React from 'react';
4
4
  /**
5
5
  * Button which launches the Scalar API Client
6
6
  */
7
- export function ScalarApiButton() {
7
+ export function ScalarApiButton({ method, path }) {
8
8
  const client = useApiClientModal();
9
9
  return (React.createElement("div", { className: "scalar scalar-activate" },
10
- React.createElement("button", { className: "scalar-activate-button", onClick: () => client?.open() },
10
+ React.createElement("button", { className: "scalar-activate-button", onClick: () => client?.open({ method, path }) },
11
11
  React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "12", fill: "none" },
12
12
  React.createElement("path", { stroke: "currentColor", strokeWidth: "1.5", d: "M1 10.05V1.43c0-.2.2-.31.37-.22l7.26 4.08c.17.1.17.33.01.43l-7.26 4.54a.25.25 0 0 1-.38-.21Z" })),
13
13
  "Test it")));
@@ -40,6 +40,25 @@ export const codeSampleGenerators = [
40
40
  return lines.map((line, index) => (index > 0 ? indent(line, 2) : line)).join(separator);
41
41
  },
42
42
  },
43
+ {
44
+ id: 'python',
45
+ label: 'Python',
46
+ syntax: 'python',
47
+ generate: ({ method, url, headers, body }) => {
48
+ let code = 'import requests\n\n';
49
+ code += `response = requests.${method.toLowerCase()}(\n`;
50
+ code += indent(`"${url}",\n`, 4);
51
+ if (headers) {
52
+ code += indent(`headers=${JSON.stringify(headers)},\n`, 4);
53
+ }
54
+ if (body) {
55
+ code += indent(`json=${JSON.stringify(body)}\n`, 4);
56
+ }
57
+ code += ')\n';
58
+ code += `data = response.json()`;
59
+ return code;
60
+ },
61
+ },
43
62
  ];
44
63
  function indent(code, spaces) {
45
64
  const indent = ' '.repeat(spaces);
@@ -52,7 +52,7 @@ export function generateSchemaExample(schema, options = {}, ancestors = new Set(
52
52
  }
53
53
  return 'text';
54
54
  }
55
- if (schema.type === 'number') {
55
+ if (schema.type === 'number' || schema.type === 'integer') {
56
56
  return schema.default || 0;
57
57
  }
58
58
  if (schema.type === 'boolean') {
@@ -70,7 +70,7 @@ export function generateSchemaExample(schema, options = {}, ancestors = new Set(
70
70
  }
71
71
  if (schema.properties) {
72
72
  const example = {};
73
- const props = onlyRequired ? schema.required ?? [] : Object.keys(schema.properties);
73
+ const props = onlyRequired ? (schema.required ?? []) : Object.keys(schema.properties);
74
74
  for (const key of props) {
75
75
  const property = noReference(schema.properties[key]);
76
76
  if (property && (onlyRequired || !property.deprecated)) {