mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Add OpenAPI servers selection (#3726)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add OpenAPI servers selection
|
||||
@@ -39,6 +39,8 @@ export function getOpenAPIContext(args: {
|
||||
chevronDown: <Icon icon="chevron-down" />,
|
||||
chevronRight: <Icon icon="chevron-right" />,
|
||||
plus: <Icon icon="plus" />,
|
||||
copy: <Icon icon="copy" />,
|
||||
check: <Icon icon="check" />,
|
||||
},
|
||||
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
||||
renderDocument: (documentProps) => (
|
||||
|
||||
@@ -478,17 +478,22 @@
|
||||
}
|
||||
|
||||
.openapi-path-server {
|
||||
@apply text-tint hidden md:inline;
|
||||
@apply text-tint inline;
|
||||
}
|
||||
|
||||
.openapi-path .openapi-method {
|
||||
@apply m-0 mt-0.5 items-center flex px-1;
|
||||
.openapi-summary .openapi-path .openapi-method {
|
||||
@apply m-0 items-center flex px-2 py-1 h-6;
|
||||
}
|
||||
|
||||
.openapi-path-title {
|
||||
@apply flex-1 relative font-normal text-left font-mono text-tint-strong/10;
|
||||
@apply py-0.5 px-1 rounded hover:bg-tint transition-colors;
|
||||
@apply flex-1 relative font-normal text-left overflow-x-auto font-mono text-tint-strong/10;
|
||||
@apply whitespace-nowrap md:whitespace-normal;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.openapi-path-title-row{
|
||||
@apply flex flex-row items-center;
|
||||
}
|
||||
|
||||
.openapi-path-title[data-deprecated="true"] {
|
||||
@@ -592,18 +597,30 @@ body:has(.openapi-select-popover) {
|
||||
}
|
||||
|
||||
.openapi-select > button {
|
||||
@apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 text-tint-strong rounded text-xs p-1.5 leading-none border border-tint-subtle bg-tint;
|
||||
@apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 p-1.5 border border-tint-subtle text-tint-strong rounded leading-none;
|
||||
@apply hover:bg-tint-hover transition-all;
|
||||
}
|
||||
|
||||
.openapi-select:not(.openapi-select-unstyled) > button {
|
||||
@apply border border-tint-subtle bg-tint text-xs;
|
||||
}
|
||||
|
||||
.openapi-select-unstyled > button {
|
||||
@apply p-1;
|
||||
}
|
||||
|
||||
.openapi-select > button[data-focused="true"] {
|
||||
@apply outline-primary -outline-offset-1 outline outline-1;
|
||||
@apply outline-primary -outline-offset-1 outline;
|
||||
}
|
||||
|
||||
.openapi-select > button > span.react-aria-SelectValue {
|
||||
@apply shrink truncate flex items-center;
|
||||
}
|
||||
|
||||
.openapi-select > button > .react-aria-SelectValue [slot="description"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.openapi-select > button .openapi-markdown {
|
||||
@apply *:leading-none;
|
||||
}
|
||||
@@ -630,6 +647,14 @@ body:has(.openapi-select-popover) {
|
||||
@apply hover:bg-tint-hover hover:theme-gradient:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
|
||||
}
|
||||
|
||||
.openapi-select-item.openapi-select-item-column {
|
||||
@apply flex flex-col gap-1 justify-start items-start;
|
||||
}
|
||||
|
||||
.openapi-select-item [slot="description"] {
|
||||
@apply text-xs text-tint-subtle;
|
||||
}
|
||||
|
||||
.openapi-select button .openapi-markdown,
|
||||
.openapi-select-item .openapi-markdown {
|
||||
@apply text-[0.813rem] *:truncate *:!p-0 *:!m-0 [&>*:not(:first-child)]:hidden;
|
||||
@@ -966,3 +991,20 @@ body:has(.openapi-select-popover) {
|
||||
.openapi-copy-button[data-disabled="true"] {
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
.openapi-path-copy-button {
|
||||
@apply p-1 flex rounded-md;
|
||||
@apply hover:bg-tint;
|
||||
}
|
||||
|
||||
.openapi-path-copy-button-icon {
|
||||
@apply size-6 flex opacity-0 transition-all;
|
||||
}
|
||||
|
||||
.openapi-path:hover .openapi-path-copy-button-icon {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.openapi-path-copy-button-icon svg {
|
||||
@apply text-tint size-4;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export function InteractiveSection(props: {
|
||||
<OpenAPISelect
|
||||
stateKey={stateKey}
|
||||
items={tabs}
|
||||
onSelectionChange={() => {
|
||||
onChange={() => {
|
||||
state.expand();
|
||||
}}
|
||||
icon={selectIcon}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client';
|
||||
import clsx from 'classnames';
|
||||
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||
import { createStateKey } from './utils';
|
||||
@@ -49,16 +48,7 @@ function MediaTypeSelector(props: {
|
||||
}));
|
||||
|
||||
return (
|
||||
<OpenAPISelect
|
||||
className={clsx('openapi-select')}
|
||||
items={renderers.map((renderer) => ({
|
||||
key: renderer.mediaType,
|
||||
label: renderer.mediaType,
|
||||
}))}
|
||||
icon={selectIcon}
|
||||
stateKey={stateKey}
|
||||
placement="bottom start"
|
||||
>
|
||||
<OpenAPISelect items={items} icon={selectIcon} stateKey={stateKey} placement="bottom start">
|
||||
{items.map((item) => (
|
||||
<OpenAPISelectItem key={item.key} id={item.key} value={item}>
|
||||
{item.label}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button, type ButtonProps, Tooltip, TooltipTrigger } from 'react-aria-components';
|
||||
import { Button, type ButtonProps } from 'react-aria-components';
|
||||
import { OpenAPITooltip } from './OpenAPITooltip';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
|
||||
@@ -36,13 +37,7 @@ export function OpenAPICopyButton(
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipTrigger
|
||||
isOpen={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
isDisabled={!withTooltip}
|
||||
closeDelay={200}
|
||||
delay={200}
|
||||
>
|
||||
<OpenAPITooltip isDisabled={!withTooltip} isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||
<Button
|
||||
type="button"
|
||||
preventFocusOnPress
|
||||
@@ -56,17 +51,16 @@ export function OpenAPICopyButton(
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
<Tooltip
|
||||
isOpen={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
placement="top"
|
||||
offset={4}
|
||||
className="openapi-tooltip"
|
||||
>
|
||||
{copied
|
||||
? t(context.translation, 'copied')
|
||||
: label || t(context.translation, 'copy_to_clipboard')}
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
<OpenAPITooltip.Content isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||
{copied ? (
|
||||
<>
|
||||
{context.icons.check}
|
||||
{t(context.translation, 'copied')}
|
||||
</>
|
||||
) : (
|
||||
label || t(context.translation, 'copy_to_clipboard')
|
||||
)}
|
||||
</OpenAPITooltip.Content>
|
||||
</OpenAPITooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ function DisclosureItem(props: {
|
||||
<OpenAPISelect
|
||||
icon={selectIcon}
|
||||
stateKey={selectStateKey}
|
||||
onSelectionChange={() => {
|
||||
onChange={() => {
|
||||
state.expand();
|
||||
}}
|
||||
items={group.tabs}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||
import { OpenAPIPathItem } from './OpenAPIPathItem';
|
||||
import { OpenAPIPathMultipleServers } from './OpenAPIPathMultipleServers';
|
||||
import { type OpenAPIUniversalContext, getOpenAPIClientContext } from './context';
|
||||
import { formatPath } from './formatPath';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
import { getDefaultServerURL } from './util/server';
|
||||
|
||||
/**
|
||||
* Display the path of an operation.
|
||||
*/
|
||||
export function OpenAPIPath(props: {
|
||||
export type OpenAPIPathProps = {
|
||||
data: OpenAPIOperationData;
|
||||
context: OpenAPIUniversalContext;
|
||||
/** Whether to show the server URL.
|
||||
* @default true
|
||||
*/
|
||||
@@ -18,73 +16,27 @@ export function OpenAPIPath(props: {
|
||||
* @default true
|
||||
*/
|
||||
canCopy?: boolean;
|
||||
}) {
|
||||
const { data, context, withServer = true, canCopy = true } = props;
|
||||
const { method, path, operation } = data;
|
||||
|
||||
const server = getDefaultServerURL(data.servers);
|
||||
const formattedPath = formatPath(path);
|
||||
|
||||
const element = (() => {
|
||||
return (
|
||||
<>
|
||||
{withServer ? <span className="openapi-path-server">{server}</span> : null}
|
||||
{formattedPath}
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="openapi-path">
|
||||
<div className={`openapi-method openapi-method-${method}`}>{method}</div>
|
||||
|
||||
<OpenAPICopyButton
|
||||
value={`${withServer ? server : ''}${path}`}
|
||||
className="openapi-path-title"
|
||||
data-deprecated={operation.deprecated}
|
||||
isDisabled={!canCopy}
|
||||
context={getOpenAPIClientContext(context)}
|
||||
>
|
||||
{element}
|
||||
</OpenAPICopyButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Format the path by wrapping placeholders in <span> tags.
|
||||
* Display the path of an operation.
|
||||
*/
|
||||
function formatPath(path: string) {
|
||||
// Matches placeholders like {id}, {userId}, etc.
|
||||
const regex = /\{\s*(\w+)\s*\}|:\w+/g;
|
||||
export function OpenAPIPath(props: OpenAPIPathProps & { context: OpenAPIUniversalContext }) {
|
||||
const { data, withServer = true, context } = props;
|
||||
const { path } = data;
|
||||
const clientContext = getOpenAPIClientContext(context);
|
||||
|
||||
const parts: (string | React.JSX.Element)[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
//Wrap the variables in <span> tags and maintain either {variable} or :variable
|
||||
path.replace(regex, (match, _, offset) => {
|
||||
if (offset > lastIndex) {
|
||||
parts.push(path.slice(lastIndex, offset));
|
||||
}
|
||||
parts.push(
|
||||
<span key={`offset-${offset}`} className="openapi-path-variable">
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
lastIndex = offset + match.length;
|
||||
return match;
|
||||
});
|
||||
|
||||
if (lastIndex < path.length) {
|
||||
parts.push(path.slice(lastIndex));
|
||||
if (withServer && data.servers.length > 1) {
|
||||
return <OpenAPIPathMultipleServers {...props} context={clientContext} />;
|
||||
}
|
||||
|
||||
const formattedPath = parts.map((part, index) => {
|
||||
if (typeof part === 'string') {
|
||||
return <span key={`part-${index}`}>{part}</span>;
|
||||
}
|
||||
return part;
|
||||
});
|
||||
const formattedPath = formatPath(path);
|
||||
const defaultServer = getDefaultServerURL(data.servers);
|
||||
|
||||
return formattedPath;
|
||||
return (
|
||||
<OpenAPIPathItem {...props} value={`${defaultServer}${path}`} context={clientContext}>
|
||||
{withServer ? <span className="openapi-path-server">{defaultServer}</span> : null}
|
||||
{formattedPath}
|
||||
</OpenAPIPathItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||
import type { OpenAPIPathProps } from './OpenAPIPath';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
|
||||
export function OpenAPIPathItem(
|
||||
props: OpenAPIPathProps & {
|
||||
value?: string;
|
||||
children: React.ReactNode;
|
||||
copyType?: 'button' | 'children';
|
||||
context: OpenAPIClientContext;
|
||||
}
|
||||
) {
|
||||
const { value, canCopy = true, context, children, data, copyType = 'children' } = props;
|
||||
const { operation, method } = data;
|
||||
|
||||
const title = <span className="openapi-path-title">{children}</span>;
|
||||
|
||||
return (
|
||||
<div className="openapi-path">
|
||||
<div className={`openapi-method openapi-method-${method}`}>{method}</div>
|
||||
{canCopy && value ? (
|
||||
copyType === 'children' ? (
|
||||
<OpenAPICopyButton
|
||||
value={value}
|
||||
data-deprecated={operation.deprecated}
|
||||
isDisabled={!canCopy}
|
||||
context={context}
|
||||
className="openapi-path-copy-button"
|
||||
>
|
||||
{title}
|
||||
</OpenAPICopyButton>
|
||||
) : (
|
||||
<>
|
||||
{title}
|
||||
<OpenAPICopyButton
|
||||
value={value}
|
||||
data-deprecated={operation.deprecated}
|
||||
isDisabled={!canCopy}
|
||||
context={context}
|
||||
className="openapi-path-copy-button openapi-path-copy-button-icon"
|
||||
>
|
||||
{context.icons.copy}
|
||||
</OpenAPICopyButton>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
import { Text } from 'react-aria-components';
|
||||
import type { OpenAPIPathProps } from './OpenAPIPath';
|
||||
import { OpenAPIPathItem } from './OpenAPIPathItem';
|
||||
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
|
||||
import { OpenAPITooltip } from './OpenAPITooltip';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { formatPath } from './formatPath';
|
||||
import type { OpenAPIServerWithCustomProperties } from './types';
|
||||
import { getDefaultServerURL } from './util/server';
|
||||
import { createStateKey } from './utils';
|
||||
|
||||
export const serversStateKey = createStateKey('servers');
|
||||
|
||||
/**
|
||||
* Display the path of an operation.
|
||||
*/
|
||||
export function OpenAPIPathMultipleServers(
|
||||
props: OpenAPIPathProps & { context: OpenAPIClientContext }
|
||||
) {
|
||||
const { data, withServer = true, context } = props;
|
||||
const { path, servers } = data;
|
||||
|
||||
const defaultServer = getDefaultServerURL(servers);
|
||||
const { key, setKey } = useSelectState(serversStateKey, defaultServer);
|
||||
const formattedPath = formatPath(path);
|
||||
|
||||
const items = servers
|
||||
.filter(
|
||||
(server): server is OpenAPIServerWithCustomProperties & { url: string } => !!server.url
|
||||
)
|
||||
.map((server) => ({
|
||||
key: server.url,
|
||||
label: server.url,
|
||||
description: server.description,
|
||||
}));
|
||||
|
||||
return (
|
||||
<OpenAPIPathItem
|
||||
copyType="button"
|
||||
{...props}
|
||||
value={`${withServer ? key : ''}${path}`}
|
||||
context={context}
|
||||
>
|
||||
{withServer ? (
|
||||
<OpenAPITooltip>
|
||||
<OpenAPISelect
|
||||
className="openapi-select openapi-select-unstyled"
|
||||
items={items}
|
||||
stateKey={serversStateKey}
|
||||
placement="bottom start"
|
||||
icon={context.icons.chevronDown}
|
||||
defaultValue={defaultServer}
|
||||
onChange={setKey}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<OpenAPISelectItem
|
||||
textValue={item.label}
|
||||
key={item.key}
|
||||
id={item.key}
|
||||
value={item}
|
||||
className="openapi-select-item-column"
|
||||
>
|
||||
<Text slot="label">{item.label}</Text>
|
||||
{item.description ? (
|
||||
<Text slot="description">{item.description}</Text>
|
||||
) : null}
|
||||
</OpenAPISelectItem>
|
||||
))}
|
||||
</OpenAPISelect>
|
||||
<OpenAPITooltip.Content>Click to select a server</OpenAPITooltip.Content>
|
||||
</OpenAPITooltip>
|
||||
) : null}
|
||||
{formattedPath}
|
||||
</OpenAPIPathItem>
|
||||
);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectPro
|
||||
/**
|
||||
* Icon to display in the select button.
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
icon?: React.ReactNode | null;
|
||||
}
|
||||
|
||||
export function useSelectState(stateKey = 'select-state', initialKey: Key = 'default') {
|
||||
@@ -42,18 +42,10 @@ export function useSelectState(stateKey = 'select-state', initialKey: Key = 'def
|
||||
}
|
||||
|
||||
export function OpenAPISelect<T extends OpenAPISelectItem>(props: OpenAPISelectProps<T>) {
|
||||
const {
|
||||
icon = '▼',
|
||||
items,
|
||||
children,
|
||||
className,
|
||||
placement,
|
||||
stateKey,
|
||||
selectedKey,
|
||||
onSelectionChange,
|
||||
} = props;
|
||||
const { icon, items, children, className, placement, stateKey, value, onChange, defaultValue } =
|
||||
props;
|
||||
|
||||
const state = useSelectState(stateKey, items[0]?.key);
|
||||
const state = useSelectState(stateKey, defaultValue ?? items[0]?.key);
|
||||
|
||||
const selected = items.find((item) => item.key === state.key) || items[0];
|
||||
|
||||
@@ -61,16 +53,16 @@ export function OpenAPISelect<T extends OpenAPISelectItem>(props: OpenAPISelectP
|
||||
<Select
|
||||
aria-label="OpenAPI Select"
|
||||
{...props}
|
||||
value={selectedKey || selected?.key}
|
||||
value={value ?? selected?.key}
|
||||
onChange={(key) => {
|
||||
onSelectionChange?.(key);
|
||||
onChange?.(key);
|
||||
state.setKey(key);
|
||||
}}
|
||||
className={clsx('openapi-select', className)}
|
||||
>
|
||||
<Button>
|
||||
<SelectValue />
|
||||
{icon}
|
||||
{icon !== null ? icon || '▼' : null}
|
||||
</Button>
|
||||
<Popover placement={placement} className="openapi-select-popover">
|
||||
<ListBox className="openapi-select-listbox" items={items}>
|
||||
@@ -86,10 +78,14 @@ export function OpenAPISelectItem(props: ListBoxItemProps) {
|
||||
<ListBoxItem
|
||||
{...props}
|
||||
className={({ isFocused, isSelected }) =>
|
||||
clsx('openapi-select-item', {
|
||||
'openapi-select-item-focused': isFocused,
|
||||
'openapi-select-item-selected': isSelected,
|
||||
})
|
||||
clsx(
|
||||
'openapi-select-item',
|
||||
{
|
||||
'openapi-select-item-focused': isFocused,
|
||||
'openapi-select-item-selected': isSelected,
|
||||
},
|
||||
props.className
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type { TooltipTriggerProps } from 'react-aria';
|
||||
import { Tooltip, type TooltipProps, TooltipTrigger } from 'react-aria-components';
|
||||
|
||||
export function OpenAPITooltip(
|
||||
props: TooltipTriggerProps & {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<TooltipTrigger {...rest} closeDelay={200} delay={200}>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPITooltipContent(props: TooltipProps) {
|
||||
const { children, placement = 'top', offset = 4, className, ...rest } = props;
|
||||
return (
|
||||
<Tooltip
|
||||
{...rest}
|
||||
placement={placement}
|
||||
offset={offset}
|
||||
className={classNames('openapi-tooltip', className)}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
OpenAPITooltip.Content = OpenAPITooltipContent;
|
||||
@@ -13,6 +13,8 @@ export interface OpenAPIClientContext {
|
||||
chevronDown: React.ReactNode;
|
||||
chevronRight: React.ReactNode;
|
||||
plus: React.ReactNode;
|
||||
copy: React.ReactNode;
|
||||
check: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Format the path by wrapping placeholders in <span> tags.
|
||||
*/
|
||||
export function formatPath(path: string) {
|
||||
// Matches placeholders like {id}, {userId}, etc.
|
||||
const regex = /\{\s*(\w+)\s*\}|:\w+/g;
|
||||
|
||||
const parts: (string | React.JSX.Element)[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
//Wrap the variables in <span> tags and maintain either {variable} or :variable
|
||||
path.replace(regex, (match, _, offset) => {
|
||||
if (offset > lastIndex) {
|
||||
parts.push(path.slice(lastIndex, offset));
|
||||
}
|
||||
parts.push(
|
||||
<span key={`offset-${offset}`} className="openapi-path-variable">
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
lastIndex = offset + match.length;
|
||||
return match;
|
||||
});
|
||||
|
||||
if (lastIndex < path.length) {
|
||||
parts.push(path.slice(lastIndex));
|
||||
}
|
||||
|
||||
const formattedPath = parts.map((part, index) => {
|
||||
if (typeof part === 'string') {
|
||||
return <span key={`part-${index}`}>{part}</span>;
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
return formattedPath;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export type OpenAPIServerVariableWithCustomProperties = OpenAPIV3.ServerVariable
|
||||
* OpenAPI ServerObject type extended to provide x-gitbook prefill custom properties at the variable level.
|
||||
*/
|
||||
export type OpenAPIServerWithCustomProperties = Omit<OpenAPIV3.ServerObject, 'variables'> & {
|
||||
name?: string;
|
||||
variables?: {
|
||||
[variable: string]: OpenAPIServerVariableWithCustomProperties;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user