diff --git a/packages/rapidoc/src/components/schema-table.ts b/packages/rapidoc/src/components/schema-table.ts index b6d68dc3..8c9c903f 100644 --- a/packages/rapidoc/src/components/schema-table.ts +++ b/packages/rapidoc/src/components/schema-table.ts @@ -409,18 +409,29 @@ export default class SchemaTable extends SchemaBaseElement { ${this.renderDescrCell(node.description)}
- ${node.options?.map( - (opt, i) => html` -
-
- OPT ${opt.optionIndex || i + 1} - ${opt.optionTitle ? html`${opt.optionTitle}` : ""} -
-
-
-
-
${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}
- `, + ${node.options?.map((opt, i) => + opt.kind === "primitive" + ? // A primitive option fits on a single row: the badge, then its type and description + html`
+ ${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)} +
` + : html` +
+
+ OPT ${opt.optionIndex || i + 1} + ${ + opt.optionTitle + ? html`${opt.optionTitle}` + : "" + } +
+
+
+
+
+ ${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)} +
+ `, )}
`; @@ -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`
- ${deprecatedIcon}${node.name ? html`${node.name}` : ""} + ${ + isOption + ? html`OPT ${node.optionIndex}${ + node.optionTitle + ? html`${node.optionTitle}` + : "" + }` + : "" + }${deprecatedIcon}${ + node.name && !isOption + ? html`${node.name}` + : "" + } ${node.required ? html`*` : ""}
diff --git a/packages/rapidoc/src/utils/schema-utils.js b/packages/rapidoc/src/utils/schema-utils.js index 60c6238c..ac852bb5 100644 --- a/packages/rapidoc/src/utils/schema-utils.js +++ b/packages/rapidoc/src/utils/schema-utils.js @@ -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) { @@ -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}"; @@ -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; }) diff --git a/tests/unit/schema-ast.test.js b/tests/unit/schema-ast.test.js index 7d99b402..b4eab7ad 100644 --- a/tests/unit/schema-ast.test.js +++ b/tests/unit/schema-ast.test.js @@ -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); diff --git a/tests/unit/schema-utils.test.js b/tests/unit/schema-utils.test.js index f62c830a..caa15b82 100644 --- a/tests/unit/schema-utils.test.js +++ b/tests/unit/schema-utils.test.js @@ -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);