Skip to content

Commit

Permalink
OpenAPI: support XML on fetcher
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Jan 4, 2025
1 parent a557bb4 commit 617718c
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 31 deletions.
2 changes: 1 addition & 1 deletion examples/openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1187,7 +1187,7 @@
"requestBody": {
"required": true,
"content": {
"application/json": {
"application/xml": {
"schema": {
"type": "object",
"properties": {
Expand Down
14 changes: 10 additions & 4 deletions packages/openapi/src/render/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,13 @@ interface Context {
export interface APIPlaygroundProps {
route: string;
method: string;
bodyType: 'json' | 'form-data';
authorization?: PrimitiveRequestField & { authType: string };
path?: PrimitiveRequestField[];
query?: PrimitiveRequestField[];
header?: PrimitiveRequestField[];
body?: RequestSchema;
body?: RequestSchema & {
mediaType: string;
};
schemas: Record<string, RequestSchema>;
proxyUrl?: string;
}
Expand Down Expand Up @@ -125,7 +126,6 @@ export function Playground({
authorization: getAuthorizationField(method, ctx),
method: method.method,
route: path,
bodyType: mediaType === 'multipart/form-data' ? 'form-data' : 'json',
path: method.parameters
?.filter((v) => v.in === 'path')
.map((v) => parameterToField(v, context)),
Expand All @@ -135,7 +135,13 @@ export function Playground({
header: method.parameters
?.filter((v) => v.in === 'header')
.map((v) => parameterToField(v, context)),
body: bodySchema,
body:
bodySchema && mediaType
? {
...bodySchema,
mediaType: mediaType as string,
}
: undefined,
schemas: context.references,
proxyUrl: ctx.proxyUrl,
};
Expand Down
58 changes: 35 additions & 23 deletions packages/openapi/src/ui/playground/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { type DynamicField } from '@/ui/contexts/schema';
export interface FetchOptions {
url: string;
method: string;
type: 'form-data' | 'json';

header: Record<string, unknown>;

body?: unknown;
body?: {
mediaType: string;
value: unknown;
};
dynamicFields?: Map<string, DynamicField>;
}

Expand All @@ -24,7 +26,7 @@ export interface Fetcher {
* @param input - fetch request inputs
* @param dynamicFields - schema of dynamic fields, given by the playground client
*/
fetch: (input: FetchOptions & {}) => Promise<FetchResult>;
fetch: (options: FetchOptions) => Promise<FetchResult>;
}

/**
Expand All @@ -36,31 +38,32 @@ export function createBrowserFetcher(
references: Record<string, RequestSchema>,
): Fetcher {
return {
async fetch(input) {
async fetch(options) {
const headers = new Headers();
if (input.type !== 'form-data')
headers.append('Content-Type', 'application/json');
if (options.body && options.body.mediaType !== 'multipart/form-data')
headers.append('Content-Type', options.body.mediaType);

for (const key of Object.keys(input.header)) {
const paramValue = input.header[key];
for (const key of Object.keys(options.header)) {
const paramValue = options.header[key];

if (typeof paramValue === 'string' && paramValue.length > 0)
headers.append(key, paramValue.toString());
}

return fetch(input.url, {
method: input.method,
return fetch(options.url, {
method: options.method,
cache: 'no-cache',
headers,
body: bodySchema
? createBodyFromValue(
input.type,
input.body,
bodySchema,
references,
input.dynamicFields ?? new Map(),
)
: undefined,
body:
bodySchema && options.body
? await createBodyFromValue(
options.body.mediaType,
options.body.value,
bodySchema,
references,
options.dynamicFields ?? new Map(),
)
: undefined,
signal: AbortSignal.timeout(10 * 1000),
})
.then(async (res) => {
Expand Down Expand Up @@ -95,19 +98,28 @@ export function createBrowserFetcher(
/**
* Create request body from value
*/
export function createBodyFromValue(
type: 'json' | 'form-data',
export async function createBodyFromValue(
mediaType: string,
value: unknown,
schema: RequestSchema,
references: Record<string, RequestSchema>,
dynamicFields: Map<string, DynamicField>,
): string | FormData {
): Promise<string | FormData> {
const result = convertValue('body', value, schema, references, dynamicFields);

if (type === 'json') {
if (mediaType === 'application/json') {
return JSON.stringify(result);
}

if (mediaType === 'application/xml') {
const { js2xml } = await import('xml-js');

return js2xml(result as Record<string, unknown>, {
compact: true,
spaces: 2,
});
}

const formData = new FormData();

if (typeof result !== 'object' || !result) {
Expand Down
9 changes: 6 additions & 3 deletions packages/openapi/src/ui/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export interface CustomField<TName extends FieldPath<FormValues>, Info> {
export function APIPlayground({
route,
method = 'GET',
bodyType,
authorization,
path = [],
header = [],
Expand Down Expand Up @@ -107,10 +106,14 @@ export function APIPlayground({
}

return fetcher.fetch({
type: bodyType,
url: url.toString(),
header,
body: input.body,
body: body
? {
mediaType: body.mediaType,
value: input.body,
}
: undefined,
dynamicFields: dynamicRef.current,
method,
});
Expand Down

0 comments on commit 617718c

Please sign in to comment.