@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.
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { CodeSampleInput, codeSampleGenerators } from './code-samples';
4
4
  import { OpenAPIOperationData } from './fetchOpenAPIOperation';
5
- import { generateMediaTypeExample } from './generateSchemaExample';
5
+ import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
6
6
  import { InteractiveSection } from './InteractiveSection';
7
7
  import { getServersURL } from './OpenAPIServerURL';
8
8
  import { ScalarApiButton } from './ScalarApiButton';
@@ -19,17 +19,51 @@ export function OpenAPICodeSample(props: {
19
19
  }) {
20
20
  const { data, context } = props;
21
21
 
22
+ const searchParams = new URLSearchParams();
23
+ const headersObject: { [k: string]: string } = {};
24
+
25
+ data.operation.parameters?.forEach((rawParam) => {
26
+ const param = noReference(rawParam);
27
+ if (!param) {
28
+ return;
29
+ }
30
+
31
+ if (param.in === 'header' && param.required) {
32
+ const example = param.schema
33
+ ? generateSchemaExample(noReference(param.schema))
34
+ : undefined;
35
+ if (example !== undefined) {
36
+ headersObject[param.name] =
37
+ typeof example !== 'string' ? JSON.stringify(example) : example;
38
+ }
39
+ } else if (param.in === 'query' && param.required) {
40
+ const example = param.schema
41
+ ? generateSchemaExample(noReference(param.schema))
42
+ : undefined;
43
+ if (example !== undefined) {
44
+ searchParams.append(
45
+ param.name,
46
+ String(Array.isArray(example) ? example[0] : example),
47
+ );
48
+ }
49
+ }
50
+ });
51
+
22
52
  const requestBody = noReference(data.operation.requestBody);
23
53
  const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;
24
54
 
25
55
  const input: CodeSampleInput = {
26
- url: getServersURL(data.servers) + data.path,
56
+ url:
57
+ getServersURL(data.servers) +
58
+ data.path +
59
+ (searchParams.size ? `?${searchParams.toString()}` : ''),
27
60
  method: data.method,
28
61
  body: requestBodyContent
29
62
  ? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
30
63
  : undefined,
31
64
  headers: {
32
65
  ...getSecurityHeaders(data.securities),
66
+ ...headersObject,
33
67
  ...(requestBodyContent
34
68
  ? {
35
69
  'Content-Type': requestBodyContent[0],
@@ -53,11 +87,19 @@ export function OpenAPICodeSample(props: {
53
87
  (['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const).forEach((key) => {
54
88
  const customSamples = data.operation[key];
55
89
  if (customSamples && Array.isArray(customSamples)) {
56
- customCodeSamples = customSamples.map((sample) => ({
57
- key: `redocly-${sample.lang}`,
58
- label: sample.label,
59
- body: <context.CodeBlock code={sample.source} syntax={sample.lang} />,
60
- }));
90
+ customCodeSamples = customSamples
91
+ .filter((sample) => {
92
+ return (
93
+ typeof sample.label === 'string' &&
94
+ typeof sample.source === 'string' &&
95
+ typeof sample.lang === 'string'
96
+ );
97
+ })
98
+ .map((sample) => ({
99
+ key: `redocly-${sample.lang}`,
100
+ label: sample.label,
101
+ body: <context.CodeBlock code={sample.source} syntax={sample.lang} />,
102
+ }));
61
103
  }
62
104
  });
63
105
 
@@ -77,7 +119,7 @@ export function OpenAPICodeSample(props: {
77
119
  tabs={samples}
78
120
  overlay={
79
121
  data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
80
- <ScalarApiButton />
122
+ <ScalarApiButton method={data.method} path={data.path} />
81
123
  )
82
124
  }
83
125
  />
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import classNames from 'classnames';
3
+ import { ApiClientModalProvider } from '@scalar/api-client-react';
3
4
 
4
5
  import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
5
6
  import { Markdown } from './Markdown';
@@ -8,7 +9,6 @@ import { OpenAPIResponseExample } from './OpenAPIResponseExample';
8
9
  import { OpenAPIServerURL } from './OpenAPIServerURL';
9
10
  import { OpenAPISpec } from './OpenAPISpec';
10
11
  import { OpenAPIClientContext, OpenAPIContextProps } from './types';
11
- import { ApiClientModalProvider } from '@scalar/api-client-react';
12
12
 
13
13
  /**
14
14
  * Display an interactive OpenAPI operation.
@@ -23,8 +23,8 @@ export function OpenAPIOperation(props: {
23
23
 
24
24
  const clientContext: OpenAPIClientContext = {
25
25
  defaultInteractiveOpened: context.defaultInteractiveOpened,
26
- specUrl: context.specUrl,
27
26
  icons: context.icons,
27
+ blockKey: context.blockKey,
28
28
  };
29
29
 
30
30
  return (
@@ -34,7 +34,9 @@ export function OpenAPIOperation(props: {
34
34
  >
35
35
  <div className={classNames('openapi-operation', className)}>
36
36
  <div className="openapi-intro">
37
- <h2 className="openapi-summary">{operation.summary}</h2>
37
+ <h2 className="openapi-summary" id={context.id}>
38
+ {operation.summary}
39
+ </h2>
38
40
  {operation.description ? (
39
41
  <Markdown className="openapi-description" source={operation.description} />
40
42
  ) : null}
@@ -3,7 +3,7 @@ import { InteractiveSection } from './InteractiveSection';
3
3
  import { OpenAPIOperationData } from './fetchOpenAPIOperation';
4
4
  import { generateSchemaExample } from './generateSchemaExample';
5
5
  import { OpenAPIContextProps } from './types';
6
- import { noReference } from './utils';
6
+ import { createStateKey, noReference } from './utils';
7
7
 
8
8
  /**
9
9
  * Display an example of the response content.
@@ -36,37 +36,52 @@ export function OpenAPIResponseExample(props: {
36
36
  }
37
37
  return Number(a) - Number(b);
38
38
  });
39
- // Take the first one
40
- const response = responses[0];
41
39
 
42
- if (!response) {
43
- return null;
44
- }
40
+ const examples = responses
41
+ .map((response) => {
42
+ const responseObject = noReference(response[1]);
45
43
 
46
- const responseObject = noReference(response[1]);
44
+ const schema = noReference(
45
+ (
46
+ responseObject.content?.['application/json'] ??
47
+ responseObject.content?.[Object.keys(responseObject.content)[0]]
48
+ )?.schema,
49
+ );
47
50
 
48
- const schema = noReference(
49
- (
50
- responseObject.content?.['application/json'] ??
51
- responseObject.content?.[Object.keys(responseObject.content)[0]]
52
- )?.schema,
53
- );
51
+ if (!schema) {
52
+ return null;
53
+ }
54
54
 
55
- if (!schema) {
56
- return null;
57
- }
55
+ const example = generateSchemaExample(schema);
56
+ if (example === undefined) {
57
+ return null;
58
+ }
59
+
60
+ return {
61
+ key: `${response[0]}`,
62
+ label: `${response[0]}`,
63
+ body: (
64
+ <context.CodeBlock
65
+ code={
66
+ typeof example === 'string' ? example : JSON.stringify(example, null, 2)
67
+ }
68
+ syntax="json"
69
+ />
70
+ ),
71
+ };
72
+ })
73
+ .filter((val): val is { key: string; label: string; body: any } => Boolean(val));
58
74
 
59
- const example = generateSchemaExample(schema);
60
- if (example === undefined) {
75
+ if (examples.length === 0) {
61
76
  return null;
62
77
  }
63
78
 
64
79
  return (
65
- <InteractiveSection header="Response" className="openapi-response-example">
66
- <context.CodeBlock
67
- code={typeof example === 'string' ? example : JSON.stringify(example, null, 2)}
68
- syntax="json"
69
- />
70
- </InteractiveSection>
80
+ <InteractiveSection
81
+ stateKey={createStateKey('response', context.blockKey)}
82
+ header="Response"
83
+ className="openapi-response-example"
84
+ tabs={examples}
85
+ />
71
86
  );
72
87
  }
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { OpenAPIV3 } from 'openapi-types';
4
- import { noReference } from './utils';
4
+ import { createStateKey, noReference } from './utils';
5
5
  import { OpenAPIResponse } from './OpenAPIResponse';
6
6
  import { OpenAPIClientContext } from './types';
7
7
  import { InteractiveSection } from './InteractiveSection';
@@ -17,6 +17,7 @@ export function OpenAPIResponses(props: {
17
17
 
18
18
  return (
19
19
  <InteractiveSection
20
+ stateKey={createStateKey('response', context.blockKey)}
20
21
  header="Response"
21
22
  className={classNames('openapi-responses')}
22
23
  tabs={Object.entries(responses).map(([statusCode, response]) => {
@@ -104,6 +104,11 @@ export function OpenAPISchemaProperty(
104
104
  Example: <code>{JSON.stringify(schema.example)}</code>
105
105
  </span>
106
106
  ) : null}
107
+ {schema.pattern ? (
108
+ <div className="openapi-schema-pattern">
109
+ Pattern: <code>{schema.pattern}</code>
110
+ </div>
111
+ ) : null}
107
112
  </div>
108
113
  }
109
114
  >
@@ -6,12 +6,15 @@ import React from 'react';
6
6
  /**
7
7
  * Button which launches the Scalar API Client
8
8
  */
9
- export function ScalarApiButton() {
9
+ export function ScalarApiButton({ method, path }: { method: string; path: string }) {
10
10
  const client = useApiClientModal();
11
11
 
12
12
  return (
13
13
  <div className="scalar scalar-activate">
14
- <button className="scalar-activate-button" onClick={() => client?.open()}>
14
+ <button
15
+ className="scalar-activate-button"
16
+ onClick={() => client?.open({ method, path })}
17
+ >
15
18
  <svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
16
19
  <path
17
20
  stroke="currentColor"
@@ -65,6 +65,25 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
65
65
  return lines.map((line, index) => (index > 0 ? indent(line, 2) : line)).join(separator);
66
66
  },
67
67
  },
68
+ {
69
+ id: 'python',
70
+ label: 'Python',
71
+ syntax: 'python',
72
+ generate: ({ method, url, headers, body }) => {
73
+ let code = 'import requests\n\n';
74
+ code += `response = requests.${method.toLowerCase()}(\n`;
75
+ code += indent(`"${url}",\n`, 4);
76
+ if (headers) {
77
+ code += indent(`headers=${JSON.stringify(headers)},\n`, 4);
78
+ }
79
+ if (body) {
80
+ code += indent(`json=${JSON.stringify(body)}\n`, 4);
81
+ }
82
+ code += ')\n';
83
+ code += `data = response.json()`;
84
+ return code;
85
+ },
86
+ },
68
87
  ];
69
88
 
70
89
  function indent(code: string, spaces: number) {
@@ -79,7 +79,7 @@ export function generateSchemaExample(
79
79
  return 'text';
80
80
  }
81
81
 
82
- if (schema.type === 'number') {
82
+ if (schema.type === 'number' || schema.type === 'integer') {
83
83
  return schema.default || 0;
84
84
  }
85
85
 
@@ -104,7 +104,7 @@ export function generateSchemaExample(
104
104
 
105
105
  if (schema.properties) {
106
106
  const example: { [key: string]: JSONValue } = {};
107
- const props = onlyRequired ? schema.required ?? [] : Object.keys(schema.properties);
107
+ const props = onlyRequired ? (schema.required ?? []) : Object.keys(schema.properties);
108
108
 
109
109
  for (const key of props) {
110
110
  const property = noReference(schema.properties[key]);
package/src/types.ts CHANGED
@@ -2,6 +2,9 @@ export type IconComponent = React.ComponentType<{ className?: string }>;
2
2
 
3
3
  export interface OpenAPIContextProps extends OpenAPIClientContext {
4
4
  CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
5
+
6
+ /** Spec url for the Scalar Api Client */
7
+ specUrl: string;
5
8
  }
6
9
 
7
10
  export interface OpenAPIClientContext {
@@ -10,14 +13,17 @@ export interface OpenAPIClientContext {
10
13
  chevronRight: React.ReactNode;
11
14
  };
12
15
 
13
- /** Spec url foor the Scalar Api Client */
14
- specUrl: string;
15
-
16
16
  /**
17
17
  * Force all sections to be opened by default.
18
18
  * @default false
19
19
  */
20
20
  defaultInteractiveOpened?: boolean;
21
+ /**
22
+ * The key of the block
23
+ */
24
+ blockKey?: string;
25
+ /** Optional id attached to the OpenAPI Operation heading and used as an anchor */
26
+ id?: string;
21
27
  }
22
28
 
23
29
  export interface OpenAPIFetcher {
package/src/utils.ts CHANGED
@@ -7,3 +7,7 @@ export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
7
7
 
8
8
  return input;
9
9
  }
10
+
11
+ export function createStateKey(key: string, scope?: string) {
12
+ return scope ? `${scope}_${key}` : key;
13
+ }