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
52 changes: 39 additions & 13 deletions packages/rapidoc/src/components/schema-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,18 +409,29 @@ export default class SchemaTable extends SchemaBaseElement {
${this.renderDescrCell(node.description)}
</div>
<div class="object-body">
${node.options?.map(
(opt, i) => html`
<div class="tr expanded xxx-of-option" data-obj="OPTION ${opt.optionIndex || i + 1}">
<div class="td key" style="padding-left:${leftPadding + 16}px">
<span class="xxx-of-key">OPT ${opt.optionIndex || i + 1}</span>
${opt.optionTitle ? html`<span class="xxx-of-descr">${opt.optionTitle}</span>` : ""}
</div>
<div class="td key-type"></div>
<div class="td key-descr"></div>
</div>
<div class="object-body">${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}</div>
`,
${node.options?.map((opt, i) =>
opt.kind === "primitive"
? // A primitive option fits on a single row: the badge, then its type and description
html`<div class="object-body">
${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}
</div>`
: html`
<div class="tr expanded xxx-of-option" data-obj="OPTION ${opt.optionIndex || i + 1}">
<div class="td key" style="padding-left:${leftPadding + 16}px">
<span class="xxx-of-key">OPT ${opt.optionIndex || i + 1}</span>
${
opt.optionTitle
? html`<span class="xxx-of-descr">${opt.optionTitle}</span>`
: ""
}
</div>
<div class="td key-type"></div>
<div class="td key-descr"></div>
</div>
<div class="object-body">
${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}
</div>
`,
)}
</div>
`;
Expand All @@ -429,11 +440,26 @@ export default class SchemaTable extends SchemaBaseElement {
// 4. Primitive kind
if (node.kind === "primitive") {
const dataTypeCss = this.getDataTypeCss(node.type);
// Primitive option of a oneOf / anyOf: the OPT n badge (and the title) takes the place of the name
const isOption = parentType === "xxx-of-option" && !!node.optionIndex;

return html`
<div class="tr primitive ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
<div class="td key ${node.deprecated ? "deprecated" : ""}" style="padding-left:${leftPadding}px">
${deprecatedIcon}${node.name ? html`<span class="key-label" title="${node.name}">${node.name}</span>` : ""}
${
isOption
? html`<span class="xxx-of-key">OPT ${node.optionIndex}</span
>${
node.optionTitle
? html`<span class="xxx-of-descr">${node.optionTitle}</span>`
: ""
}`
: ""
}${deprecatedIcon}${
node.name && !isOption
? html`<span class="key-label" title="${node.name}">${node.name}</span>`
: ""
}
${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
</div>
<div class="td key-type ${dataTypeCss}" title="${readWriteTip || nothing}">
Expand Down
23 changes: 22 additions & 1 deletion packages/rapidoc/src/utils/schema-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ export function isBinaryFileField(schema) {
);
}

// Keywords that only annotate a schema, without telling anything about the type of the value
const ANNOTATION_KEYWORDS = new Set([
"title",
"description",
"example",
"examples",
"default",
"deprecated",
"readOnly",
"writeOnly",
"externalDocs",
]);

function isAnnotationOnly(schema) {
return Object.keys(schema).every((key) => ANNOTATION_KEYWORDS.has(key) || key.startsWith("x-"));
}

/* Generates an schema object containing type and constraint info */
export function getTypeInfo(schema) {
if (!schema) {
Expand Down Expand Up @@ -170,7 +187,7 @@ export function getTypeInfo(schema) {
}
});
dataType = subTypes.length > 0 ? subTypes.join("┃") : "{missing-type-info}";
} else if (Object.keys(schema).length === 0) {
} else if (isAnnotationOnly(schema)) {
dataType = "any";
} else {
dataType = "{missing-type-info}";
Expand Down Expand Up @@ -1502,6 +1519,10 @@ function composeSchemaAST(schema, level, name, isRequired, suffix) {
optAst.writeOnly = false;
optAst.optionIndex = idx + 1;
optAst.optionTitle = optTitle;
if (optAst.kind === "primitive" && !optTitle) {
// The OPT n badge already names it, a row labelled "Option n" would only repeat it
optAst.name = "";
}
}
return optAst;
})
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/schema-ast.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,27 @@ describe("schemaToAST", () => {
});
});

describe("oneOf options", () => {
it("should not name the untitled primitive options, but keep the name of the other options", () => {
const ast = schemaToAST({
oneOf: [
{ type: "string" },
{ type: "number", title: "Count" },
{ type: "object", properties: { id: { type: "string" } } },
],
});
assert.strictEqual(ast.kind, "union");
assert.deepStrictEqual(
ast.options.map((o) => [o.optionIndex, o.name]),
[
[1, ""],
[2, "Count"],
[3, "Option 3"],
],
);
});
});

describe("recursion limit & null inputs", () => {
it("should return null for falsy schema input", () => {
assert.strictEqual(schemaToAST(null), null);
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/schema-utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,19 @@ describe("generateExample", () => {
});

describe("getTypeInfo", () => {
it("should return any for a schema that only carries annotations", () => {
assert.strictEqual(getTypeInfo({}).type, "any");
assert.strictEqual(getTypeInfo({ description: "A string, a number or a boolean" }).type, "any");
assert.strictEqual(
getTypeInfo({ title: "Value", description: "Anything", example: 1, "x-internal": true }).type,
"any",
);
});

it("should still report a missing type when the schema has constraints but no type", () => {
assert.strictEqual(getTypeInfo({ minLength: 3 }).type, "{missing-type-info}");
});

it("should format non-string const values as strings in allowedValues", () => {
const numSchema = { type: "number", const: 1 };
const info = getTypeInfo(numSchema);
Expand Down
Loading