Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/component/ApiReference/TryItModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Highlight, themes } from 'prism-react-renderer';
import MethodBadge from './MethodBadge';
import InlineText from './InlineText';
import styles from './TryItModal.module.css';
import { LANGUAGES, generateCodeExample, LangDropdownPortal, LangSelectorButton } from './langUtils';
import { LANGUAGES, generateCodeExample, LangDropdownPortal, LangSelectorButton, coerceBodyValue } from './langUtils';

const githubWithGreenKeys = {
...themes.github,
Expand Down Expand Up @@ -97,7 +97,7 @@ function buildCurl(endpoint, username, password, params, baseUrl) {
const contentType = endpoint.requestBody?.contentType || 'application/json';
let bodyLine = '';
if (bodyProps.length > 0) {
const bodyEntries = bodyProps.map((p) => [p.name, params[`__body__${p.name}`] || '']);
const bodyEntries = bodyProps.map((p) => [p.name, coerceBodyValue(params[`__body__${p.name}`] || '', p.type)]);
if (contentType === 'multipart/form-data') {
bodyLine = bodyEntries
.filter(([, v]) => v)
Expand Down Expand Up @@ -358,7 +358,7 @@ export default function TryItModal({ endpoint, onClose, selectedLang: selectedLa
fetchBody = fd;
// Don't set Content-Type for FormData — browser sets it with boundary
} else {
const bodyObj = Object.fromEntries(bodyProps.map((p) => [p.name, params[`__body__${p.name}`] || '']).filter(([, v]) => v));
const bodyObj = Object.fromEntries(bodyProps.map((p) => [p.name, coerceBodyValue(params[`__body__${p.name}`] || '', p.type)]).filter(([, v]) => v));
if (Object.keys(bodyObj).length) {
fetchBody = JSON.stringify(bodyObj);
fetchHeaders['Content-Type'] = 'application/json';
Expand Down Expand Up @@ -557,6 +557,7 @@ export default function TryItModal({ endpoint, onClose, selectedLang: selectedLa
description={p.description}
enumValues={p.enum}
value={params[`__body__${p.name}`] || ''} onChange={(v) => updateParam(`__body__${p.name}`, v)}
placeholder={(p.type || '').toLowerCase().includes('array') ? 'e.g. ["val1", "val2"] or val1, val2' : undefined}
/>
))}
</CollapsibleSection>
Expand Down
44 changes: 39 additions & 5 deletions src/component/ApiReference/langUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ function safeBase64(str) {
}
}

// Coerce a string value from an input field to the correct JS type based on the schema type.
// This ensures array/object/boolean/number fields are sent as their proper types, not strings.
export function coerceBodyValue(raw, type) {
if (!raw && raw !== 0 && raw !== false) return raw;
const t = (type || '').toLowerCase();
if (t.includes('array') || t.includes('object')) {
if (typeof raw === 'string') {
const trimmed = raw.trim();
if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
try { return JSON.parse(trimmed); } catch { /* fall through */ }
}
// Treat comma-separated values as a string array for array types
if (t.includes('array') && trimmed) {
return trimmed.split(',').map((s) => s.trim()).filter(Boolean);
}
}
return raw;
}
if (t.includes('integer') || t.includes('number')) {
const n = Number(raw);
return isNaN(n) ? raw : n;
}
if (t === 'boolean') {
if (raw === 'true') return true;
if (raw === 'false') return false;
}
return raw;
}

// prism language mapping — only use languages bundled in prism-react-renderer
export const LANGUAGES = [
{ label: 'cURL', prism: 'clike' },
Expand Down Expand Up @@ -48,11 +77,16 @@ export function generateCodeExample(endpoint, language, { username, password, pa
const isMultipart = contentType === 'multipart/form-data';
const bodyExample = bodyProps.length > 0
? Object.fromEntries(bodyProps.map((p) => {
const val = (params && params[`__body__${p.name}`]) ||
(p.type.includes('integer') || p.type.includes('number') ? 0 :
p.type.includes('boolean') ? true :
p.type.includes('array') ? [] :
`<${p.name}>`);
const raw = params && params[`__body__${p.name}`];
let val;
if (raw) {
val = coerceBodyValue(raw, p.type);
} else {
val = p.type.includes('integer') || p.type.includes('number') ? 0 :
p.type.includes('boolean') ? true :
p.type.includes('array') ? [] :
`<${p.name}>`;
}
return [p.name, val];
}))
: null;
Expand Down
32 changes: 32 additions & 0 deletions src/theme/DocItem/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, {type ReactNode} from 'react';
import Layout from '@theme-original/DocItem/Layout';
import Head from '@docusaurus/Head';
import CopyPageButton from '@site/src/component/CopyPageButton/CopyPageButton';
import type LayoutType from '@theme/DocItem/Layout';
import type {WrapperProps} from '@docusaurus/types';
import {useLocation} from '@docusaurus/router';
import styles from './styles.module.css';

type Props = WrapperProps<typeof LayoutType>;

export default function LayoutWrapper(props: Props): ReactNode {
const location = useLocation();

// Build canonical using testmu.ai + current path
const canonical = `https://www.testmuai.com${location.pathname}`;
return (
<>
{canonical && (
<Head>
<link rel="canonical" href={canonical} />
</Head>
)}
<div className={styles.docLayoutWrapper}>
<div className={styles.copyBtnRow}>
<CopyPageButton />
</div>
<Layout {...props} />
</div>
</>
);
}
Loading