mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 05:23:40 +00:00
OpenAPI: workaround ajv limitations
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
workaround ajv limitations
|
||||
@@ -33,12 +33,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/ui/components/select';
|
||||
import { labelVariants } from '@/ui/components/input';
|
||||
import {
|
||||
getPreferredType,
|
||||
type SecurityEntry,
|
||||
type NoReference,
|
||||
type ParsedSchema,
|
||||
} from '@/utils/schema';
|
||||
import { getPreferredType, type SecurityEntry, type ParsedSchema } from '@/utils/schema';
|
||||
import ServerSelect from './components/server-select';
|
||||
import { useStorageKey } from '@/ui/client/storage-key';
|
||||
import {
|
||||
@@ -52,13 +47,12 @@ import {
|
||||
} from '@fumari/stf';
|
||||
import { objectGet, objectSet, stringifyFieldKey } from '@fumari/stf/lib/utils';
|
||||
import { FieldInput, FieldSet, JsonInput, ObjectInput } from './components/inputs';
|
||||
import type { Document, HttpMethods, OperationObject, ParameterObject } from '@/types';
|
||||
import type { Document, HttpMethods, ParameterObject } from '@/types';
|
||||
import { ClientCodeBlock } from '@/ui/components/codeblock';
|
||||
import { useTranslations } from '@/ui/client/i18n';
|
||||
import { useOperationContext } from '@/ui/operation/client';
|
||||
import { OauthDialog, OauthDialogTrigger } from './components/oauth-dialog';
|
||||
import { dereferenceDocument } from '@/utils/document/dereference';
|
||||
import type { ProcessedDocument } from '@/utils/document/process';
|
||||
import { dereferenceSwallow } from '@/utils/schema/dereference';
|
||||
|
||||
export interface FormValues extends Record<string, unknown> {
|
||||
path: Record<string, unknown>;
|
||||
@@ -72,14 +66,8 @@ export interface PlaygroundClientProps extends ComponentProps<'form'>, SchemaSco
|
||||
route: string;
|
||||
method: HttpMethods;
|
||||
securities: SecurityEntry[][];
|
||||
/** the OpenAPI document */
|
||||
doc:
|
||||
| {
|
||||
bundled: Document;
|
||||
}
|
||||
| {
|
||||
processed: ProcessedDocument;
|
||||
};
|
||||
/** the OpenAPI document (not dereferenced) */
|
||||
doc: Document;
|
||||
proxyUrl?: string;
|
||||
}
|
||||
|
||||
@@ -135,19 +123,6 @@ interface RequestBodyInfo {
|
||||
mediaType: string;
|
||||
}
|
||||
|
||||
function getBodyInfo(operation: NoReference<OperationObject>): RequestBodyInfo | undefined {
|
||||
const content = operation.requestBody?.content;
|
||||
if (!content) return;
|
||||
const mediaType = getPreferredType(content);
|
||||
|
||||
if (mediaType) {
|
||||
return {
|
||||
mediaType,
|
||||
schema: content[mediaType].schema ?? true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function PlaygroundClient({
|
||||
route,
|
||||
method,
|
||||
@@ -159,13 +134,29 @@ export default function PlaygroundClient({
|
||||
...rest
|
||||
}: PlaygroundClientProps) {
|
||||
const t = useTranslations();
|
||||
const schema =
|
||||
'processed' in doc
|
||||
? doc.processed
|
||||
: // oxlint-disable-next-line eslint-plugin-react-hooks/rules-of-hooks -- assume unchanged
|
||||
useMemo(() => dereferenceDocument(doc.bundled), [doc.bundled]);
|
||||
const operation: NoReference<OperationObject> = schema.dereferenced.paths![route]![method]!;
|
||||
const body = getBodyInfo(operation);
|
||||
const { parameters, body } = useMemo(() => {
|
||||
const operation = doc.paths![route]![method]!;
|
||||
const parameters: ParameterObject[] =
|
||||
operation.parameters?.map((param) => dereferenceSwallow(param, doc)) ?? [];
|
||||
let body: RequestBodyInfo | undefined;
|
||||
|
||||
if (operation.requestBody) {
|
||||
const content = dereferenceSwallow(operation.requestBody, doc).content;
|
||||
const mediaType = content ? getPreferredType(content) : undefined;
|
||||
|
||||
if (content && mediaType) {
|
||||
body = {
|
||||
mediaType,
|
||||
schema: dereferenceSwallow(content[mediaType], doc).schema ?? true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
body,
|
||||
parameters,
|
||||
};
|
||||
}, [doc, route, method]);
|
||||
|
||||
const { example: exampleId, examples, setExampleData } = useOperationContext();
|
||||
const { server } = useServerContext();
|
||||
@@ -220,7 +211,7 @@ export default function PlaygroundClient({
|
||||
const encoded = encodeRequestData(
|
||||
{ ...mapInputs(input), method, bodyMediaType: body?.mediaType },
|
||||
mediaAdapters,
|
||||
operation.parameters,
|
||||
parameters,
|
||||
);
|
||||
return fetcher.fetch(
|
||||
joinURL(
|
||||
@@ -255,7 +246,7 @@ export default function PlaygroundClient({
|
||||
method,
|
||||
bodyMediaType: body?.mediaType,
|
||||
};
|
||||
setExampleData(data, encodeRequestData(data, mediaAdapters, operation.parameters));
|
||||
setExampleData(data, encodeRequestData(data, mediaAdapters, parameters));
|
||||
},
|
||||
timerRef.current ? 400 : 0,
|
||||
);
|
||||
@@ -277,7 +268,7 @@ export default function PlaygroundClient({
|
||||
|
||||
return (
|
||||
<StfProvider value={stf}>
|
||||
<SchemaProvider schema={schema} writeOnly={writeOnly} readOnly={readOnly}>
|
||||
<SchemaProvider doc={doc} writeOnly={writeOnly} readOnly={readOnly}>
|
||||
<form
|
||||
{...rest}
|
||||
className={cn(
|
||||
@@ -314,7 +305,7 @@ export default function PlaygroundClient({
|
||||
))}
|
||||
</SecurityTabs>
|
||||
)}
|
||||
<ParametersForm parameters={operation.parameters} />
|
||||
<ParametersForm parameters={parameters} />
|
||||
{body && (
|
||||
<CollapsiblePanel data-type="body" title={t.body}>
|
||||
{renderBodyField ? renderBodyField('body', body) : <BodyInput field={body.schema} />}
|
||||
@@ -441,7 +432,7 @@ function ParameterItem({ type, parameters }: { type: ParamType; parameters: Para
|
||||
});
|
||||
}
|
||||
|
||||
function ParametersForm({ parameters = [] }: { parameters?: ParameterObject[] }) {
|
||||
function ParametersForm({ parameters }: { parameters: ParameterObject[] }) {
|
||||
const { components: { CollapsiblePanel = DefaultCollapsiblePanel } = {} } =
|
||||
useApiContext().client.playground ?? {};
|
||||
const t = useTranslations();
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Ajv2020 } from 'ajv/dist/2020';
|
||||
import { createContext, ReactNode, use, useMemo } from 'react';
|
||||
import type { ParsedSchema, ResolvedSchema } from '@/utils/schema';
|
||||
import { mergeAllOf } from '@/utils/merge-schema';
|
||||
import type { ParsedSchema } from '@/utils/schema';
|
||||
import { mergeAllOf } from '@/utils/schema/merge';
|
||||
import { FieldKey, useDataEngine, useFieldValue, useNamespace } from '@fumari/stf';
|
||||
import { stringifyFieldKey } from '@fumari/stf/lib/utils';
|
||||
import { sample } from 'openapi-sampler';
|
||||
import { FormatFlags, schemaToString } from '@/utils/schema/to-string';
|
||||
import { DereferencedDocument } from '@/utils/document/dereference';
|
||||
import { dereferenceSwallow } from '@/utils/schema/dereference';
|
||||
import type { Document } from '@/types';
|
||||
|
||||
interface SchemaContextType extends SchemaScope {
|
||||
schema: DereferencedDocument;
|
||||
/** (not dereferenced) */
|
||||
doc: Document;
|
||||
ajv: Ajv2020;
|
||||
}
|
||||
|
||||
@@ -45,7 +47,7 @@ export const anyFields = {
|
||||
} satisfies ParsedSchema;
|
||||
|
||||
export function SchemaProvider({
|
||||
schema,
|
||||
doc,
|
||||
readOnly,
|
||||
writeOnly,
|
||||
children,
|
||||
@@ -62,10 +64,7 @@ export function SchemaProvider({
|
||||
|
||||
return (
|
||||
<SchemaContext.Provider
|
||||
value={useMemo(
|
||||
() => ({ schema, ajv, readOnly, writeOnly }),
|
||||
[schema, ajv, readOnly, writeOnly],
|
||||
)}
|
||||
value={useMemo(() => ({ doc, ajv, readOnly, writeOnly }), [doc, ajv, readOnly, writeOnly])}
|
||||
>
|
||||
{children}
|
||||
</SchemaContext.Provider>
|
||||
@@ -92,7 +91,7 @@ export function useFieldInfo(
|
||||
schema: Exclude<ParsedSchema, boolean>;
|
||||
updateInfo: (value: Partial<FieldInfo>) => void;
|
||||
} {
|
||||
const { ajv } = use(SchemaContext)!;
|
||||
const { ajv, doc } = use(SchemaContext)!;
|
||||
const engine = useDataEngine();
|
||||
const { generateDefault } = useSchemaUtils();
|
||||
const fieldData = useNamespace({
|
||||
@@ -106,7 +105,9 @@ export function useFieldInfo(
|
||||
if (union) {
|
||||
const [members, field] = union;
|
||||
|
||||
out.oneOf = members.findIndex((item) => ajv.validate(item, value));
|
||||
out.oneOf = members.findIndex(
|
||||
(item) => typeof item === 'object' && ajv.validate({ ...doc, ...item }, value),
|
||||
);
|
||||
if (out.oneOf === -1) out.oneOf = 0;
|
||||
out.unionField = field;
|
||||
}
|
||||
@@ -116,7 +117,7 @@ export function useFieldInfo(
|
||||
|
||||
out.selectedType =
|
||||
types.find((type) => {
|
||||
return ajv.validate({ ...schema, type }, value);
|
||||
return ajv.validate({ ...doc, ...schema, type }, value);
|
||||
}) ?? types[0];
|
||||
}
|
||||
|
||||
@@ -154,35 +155,51 @@ export function useFieldInfo(
|
||||
}
|
||||
|
||||
export function useSchemaUtils() {
|
||||
const { schema, readOnly } = use(SchemaContext)!;
|
||||
const { doc, readOnly } = use(SchemaContext)!;
|
||||
|
||||
return {
|
||||
generateDefault(schema: ParsedSchema): unknown {
|
||||
return sample(schema as never, {
|
||||
skipNonRequired: true,
|
||||
skipReadOnly: !readOnly,
|
||||
quiet: true,
|
||||
});
|
||||
return sample(
|
||||
schema as never,
|
||||
{
|
||||
skipNonRequired: true,
|
||||
skipReadOnly: !readOnly,
|
||||
quiet: true,
|
||||
},
|
||||
doc,
|
||||
);
|
||||
},
|
||||
schemaToString(value: ResolvedSchema, flags?: FormatFlags) {
|
||||
return schemaToString(value, schema, flags);
|
||||
schemaToString(value: ParsedSchema, flags?: FormatFlags) {
|
||||
return schemaToString(
|
||||
value,
|
||||
(raw) => ({
|
||||
raw,
|
||||
dereferenced: dereferenceSwallow(raw, doc),
|
||||
}),
|
||||
flags,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* merge `allOf`.
|
||||
* dereference & merge `allOf`.
|
||||
*/
|
||||
export function useResolvedSchema(raw: ParsedSchema): Exclude<ParsedSchema, boolean> {
|
||||
const { doc } = use(SchemaContext)!;
|
||||
return useMemo(() => {
|
||||
let out = raw;
|
||||
let out = dereferenceSwallow(raw, doc);
|
||||
|
||||
if (typeof out === 'object' && out.allOf) {
|
||||
out = mergeAllOf(out);
|
||||
out = mergeAllOf(out, {
|
||||
dereference(schema) {
|
||||
return dereferenceSwallow(schema, doc);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return typeof out === 'boolean' ? anyFields : out;
|
||||
}, [raw]);
|
||||
}, [doc, raw]);
|
||||
}
|
||||
|
||||
function getUnion(
|
||||
|
||||
@@ -253,12 +253,7 @@ export function createAPIPage(
|
||||
route={path}
|
||||
securities={parseSecurities(method, ctx.schema.dereferenced)}
|
||||
method={method.method}
|
||||
doc={{
|
||||
bundled: pickSchema(
|
||||
ctx.schema.bundled,
|
||||
encodeInternalRef(['paths', path, method.method]),
|
||||
),
|
||||
}}
|
||||
doc={pickSchema(ctx.schema.bundled, encodeInternalRef(['paths', path, method.method]))}
|
||||
proxyUrl={ctx.proxyUrl}
|
||||
writeOnly
|
||||
readOnly={false}
|
||||
|
||||
@@ -90,7 +90,7 @@ export function createClientAPIPage({
|
||||
route={path}
|
||||
securities={parseSecurities(method, ctx.schema.dereferenced)}
|
||||
method={method.method}
|
||||
doc={{ processed: ctx.schema }}
|
||||
doc={ctx.schema.bundled}
|
||||
proxyUrl={ctx.proxyUrl}
|
||||
writeOnly
|
||||
readOnly={false}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo, type ReactNode } from 'react';
|
||||
import type { ResolvedSchema } from '@/utils/schema';
|
||||
import type { ParsedSchema } from '@/utils/schema';
|
||||
import type { RenderContext } from '@/types';
|
||||
import { FormatFlags, schemaToString } from '@/utils/schema/to-string';
|
||||
import { mergeAllOf } from '@/utils/merge-schema';
|
||||
import { mergeAllOf } from '@/utils/schema/merge';
|
||||
import type { SchemaUIProps } from '@/ui/schema/client';
|
||||
import { I18nLabel } from '../client/i18n';
|
||||
|
||||
@@ -59,7 +59,7 @@ export type SchemaData = FieldBase &
|
||||
);
|
||||
|
||||
export interface SchemaUIOptions {
|
||||
root: ResolvedSchema;
|
||||
root: ParsedSchema;
|
||||
client: Omit<SchemaUIProps, 'generated'>;
|
||||
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ export function Schema({
|
||||
}
|
||||
|
||||
export function generateSchemaUI(
|
||||
root: ResolvedSchema,
|
||||
root: ParsedSchema,
|
||||
readOnly = false,
|
||||
writeOnly = false,
|
||||
ctx: RenderContext,
|
||||
@@ -107,7 +107,7 @@ export function generateSchemaUI(
|
||||
const refs: Record<string, SchemaData> = {};
|
||||
const { showExample = false } = ctx.schemaUI ?? {};
|
||||
|
||||
function generateInfoTags(schema: Exclude<ResolvedSchema, boolean>) {
|
||||
function generateInfoTags(schema: Exclude<ParsedSchema, boolean>) {
|
||||
const fields: InfoTag[] = [];
|
||||
|
||||
if (schema.default !== undefined) {
|
||||
@@ -176,8 +176,8 @@ export function generateSchemaUI(
|
||||
}
|
||||
|
||||
let _counter = 0;
|
||||
const autoIds = new WeakMap<Exclude<ResolvedSchema, boolean>, string>();
|
||||
function getSchemaId(schema: ResolvedSchema): string {
|
||||
const autoIds = new WeakMap<Exclude<ParsedSchema, boolean>, string>();
|
||||
function getSchemaId(schema: ParsedSchema): string {
|
||||
if (typeof schema === 'boolean') return String(schema);
|
||||
const raw = ctx.schema.getRawRef(schema);
|
||||
if (raw) return raw;
|
||||
@@ -190,14 +190,14 @@ export function generateSchemaUI(
|
||||
return generated;
|
||||
}
|
||||
|
||||
function isVisible(schema: ResolvedSchema): boolean {
|
||||
function isVisible(schema: ParsedSchema): boolean {
|
||||
if (typeof schema === 'boolean') return true;
|
||||
if (schema.writeOnly) return writeOnly;
|
||||
if (schema.readOnly) return readOnly;
|
||||
return true;
|
||||
}
|
||||
|
||||
function base(schema: ResolvedSchema): FieldBase {
|
||||
function base(schema: ParsedSchema): FieldBase {
|
||||
if (typeof schema === 'boolean') {
|
||||
const name = schema ? 'any' : 'never';
|
||||
return {
|
||||
@@ -215,7 +215,7 @@ export function generateSchemaUI(
|
||||
};
|
||||
}
|
||||
|
||||
function scanRefs(id: string, schema: ResolvedSchema) {
|
||||
function scanRefs(id: string, schema: ParsedSchema) {
|
||||
if (id in refs) return;
|
||||
if (typeof schema === 'boolean') {
|
||||
refs[id] = {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
||||
import { resolveRefSync } from './resolve-ref';
|
||||
import type { NoReferenceSwallow } from '.';
|
||||
import { isPlainObject } from '../is-plain-object';
|
||||
|
||||
/**
|
||||
* Resolves all $ref pointers in a schema and returns a new schema without any $ref pointers.
|
||||
@@ -13,26 +15,18 @@ export function dereferenceSync(
|
||||
const cloned = structuredClone(schema);
|
||||
|
||||
function resolve(current: unknown): JSONSchema {
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
// make sure we don't visit the same node twice
|
||||
if (visitedNodes.has(current)) {
|
||||
return current;
|
||||
// make sure we don't visit the same node twice
|
||||
if (visitedNodes.has(current)) {
|
||||
return current as never;
|
||||
}
|
||||
visitedNodes.add(current);
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
for (let index = 0; index < current.length; index++) {
|
||||
current[index] = resolve(current[index]);
|
||||
}
|
||||
visitedNodes.add(current);
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
// array
|
||||
for (let index = 0; index < current.length; index++) {
|
||||
current[index] = resolve(current[index]);
|
||||
}
|
||||
|
||||
return current as JSONSchema;
|
||||
}
|
||||
|
||||
const obj = current as Record<string, unknown>;
|
||||
|
||||
// object
|
||||
if ('$ref' in current && typeof current['$ref'] === 'string') {
|
||||
} else if (isPlainObject(current)) {
|
||||
if (typeof current.$ref === 'string') {
|
||||
const ref = current['$ref'];
|
||||
delete current['$ref'];
|
||||
const resolved = resolve(resolveRefSync(ref, cloned) as JSONSchema);
|
||||
@@ -42,13 +36,13 @@ export function dereferenceSync(
|
||||
if (typeof resolved === 'boolean') throw new Error('invalid schema');
|
||||
for (const k in resolved) {
|
||||
if (!(k in current)) {
|
||||
obj[k] = resolved[k as never];
|
||||
current[k] = resolved[k as never];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in current) {
|
||||
obj[key] = resolve(obj[key]);
|
||||
current[key] = resolve(current[key]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,3 +51,17 @@ export function dereferenceSync(
|
||||
|
||||
return resolve(cloned);
|
||||
}
|
||||
|
||||
export function dereferenceSwallow<T>(schema: T, full: unknown): NoReferenceSwallow<T> {
|
||||
if (isPlainObject(schema)) {
|
||||
if (typeof schema.$ref !== 'string') return schema as never;
|
||||
|
||||
const { $ref, ...rest } = schema;
|
||||
const resolved = dereferenceSwallow(resolveRefSync($ref, full), full);
|
||||
|
||||
if (typeof resolved !== 'object') throw new Error(`invalid schema referenced via "${$ref}"`);
|
||||
return { ...resolved, ...rest } as never;
|
||||
}
|
||||
|
||||
return schema as never;
|
||||
}
|
||||
|
||||
@@ -25,18 +25,13 @@ export type NoReference<T> = T extends (infer I)[]
|
||||
}
|
||||
: T;
|
||||
|
||||
type NoReferenceJSONSchema<T> = T extends (infer I)[]
|
||||
? NoReference<I>[]
|
||||
: T extends { $ref?: string }
|
||||
? Omit<T, '$ref'>
|
||||
: T;
|
||||
export type NoReferenceSwallow<T> = T extends ReferenceObject ? Exclude<T, ReferenceObject> : T;
|
||||
|
||||
export type ParsedSchema =
|
||||
| (JSONSchema & {
|
||||
'x-playground-lazy'?: boolean;
|
||||
})
|
||||
| boolean;
|
||||
export type ResolvedSchema = NoReferenceJSONSchema<ParsedSchema>;
|
||||
|
||||
export function getPreferredType(body: Record<string, unknown>): string | undefined {
|
||||
if ('application/json' in body) return 'application/json';
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { type ParsedSchema } from '@/utils/schema';
|
||||
import { deepEqual } from './deep-equal';
|
||||
import { deepEqual } from '../deep-equal';
|
||||
|
||||
interface Options {
|
||||
dereference?: (schema: ParsedSchema) => ParsedSchema | undefined;
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ParsedSchema, ResolvedSchema } from '@/utils/schema';
|
||||
import type { ParsedSchema } from '@/utils/schema';
|
||||
import type { DereferencedDocument } from '@/utils/document/dereference';
|
||||
|
||||
export enum FormatFlags {
|
||||
@@ -6,13 +6,14 @@ export enum FormatFlags {
|
||||
UseAlias = 1 << 0,
|
||||
}
|
||||
|
||||
type Resolver = (schema: ResolvedSchema) => {
|
||||
dereferenced: ResolvedSchema;
|
||||
type Resolver = (schema: ParsedSchema) => {
|
||||
/** swallowly dereferenced schema */
|
||||
dereferenced: ParsedSchema;
|
||||
raw?: ParsedSchema;
|
||||
};
|
||||
|
||||
export function schemaToString(
|
||||
value: ResolvedSchema,
|
||||
value: ParsedSchema,
|
||||
_resolver?: DereferencedDocument | Resolver,
|
||||
flags: FormatFlags = FormatFlags.None,
|
||||
): string {
|
||||
@@ -28,7 +29,7 @@ export function schemaToString(
|
||||
raw: ref ? { $ref: ref } : undefined,
|
||||
};
|
||||
};
|
||||
function union(union: readonly ResolvedSchema[], sep: string, flags: FormatFlags) {
|
||||
function union(union: readonly ParsedSchema[], sep: string, flags: FormatFlags) {
|
||||
const members = new Set();
|
||||
let nullable = false;
|
||||
|
||||
@@ -46,9 +47,8 @@ export function schemaToString(
|
||||
return nullable ? `${result} | null` : result;
|
||||
}
|
||||
|
||||
function run(schema: ResolvedSchema, flags: FormatFlags): string {
|
||||
const resolved = resolver(schema);
|
||||
schema = resolved.dereferenced;
|
||||
function run(input: ParsedSchema, flags: FormatFlags): string {
|
||||
const { dereferenced: schema, raw } = resolver(input);
|
||||
|
||||
if (schema === true) return 'any';
|
||||
else if (schema === false) return 'never';
|
||||
@@ -56,8 +56,8 @@ export function schemaToString(
|
||||
if ((flags & FormatFlags.UseAlias) === FormatFlags.UseAlias) {
|
||||
if (schema.title) return schema.title;
|
||||
|
||||
if (typeof resolved.raw === 'object' && resolved.raw.$ref) {
|
||||
const ref = resolved.raw.$ref.split('/');
|
||||
if (typeof raw === 'object' && raw.$ref) {
|
||||
const ref = raw.$ref.split('/');
|
||||
if (ref.length > 0) return ref[ref.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ import { expect, test } from 'vitest';
|
||||
import { renderContextFrom } from './utils';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'node:path';
|
||||
import type { ResolvedSchema } from '@/utils/schema';
|
||||
|
||||
const cwd = fileURLToPath(new URL('./', import.meta.url));
|
||||
|
||||
test('double-oneOf in allOf does not crash', async () => {
|
||||
const ctx = await renderContextFrom(path.join(cwd, './fixtures/double-oneof.yaml'));
|
||||
|
||||
const createItem = ctx.schema.dereferenced.components!.schemas!.CreateItem as ResolvedSchema;
|
||||
const createItem = ctx.schema.dereferenced.components!.schemas!.CreateItem;
|
||||
const out = generateSchemaUI(createItem, undefined, undefined, ctx);
|
||||
|
||||
// Should produce a cross-product of 4 oneOf variants, not crash
|
||||
@@ -22,7 +21,7 @@ test('test', async () => {
|
||||
const ctx = await renderContextFrom(path.join(cwd, './fixtures/unkey.json'));
|
||||
|
||||
const out = generateSchemaUI(
|
||||
ctx.schema.dereferenced.components!.schemas!.V1KeysVerifyKeyResponse as ResolvedSchema,
|
||||
ctx.schema.dereferenced.components!.schemas!.V1KeysVerifyKeyResponse,
|
||||
undefined,
|
||||
undefined,
|
||||
ctx,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { mergeAllOf } from '@/utils/merge-schema';
|
||||
import { mergeAllOf } from '@/utils/schema/merge';
|
||||
import { pickSchema } from '@/utils/schema/pick';
|
||||
import { joinURL, resolveRequestData, resolveServerUrl, withBase } from '@/utils/url';
|
||||
import type { RequestData } from '@/requests/types';
|
||||
|
||||
Reference in New Issue
Block a user