docusaurus-theme-openapi-docs
Version:
OpenAPI theme for Docusaurus.
828 lines (827 loc) • 26.9 kB
JavaScript
"use strict";
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */
var __importDefault =
(this && this.__importDefault) ||
function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importDefault(require("react"));
const ArrayBrackets_1 = require("@theme/ArrayBrackets");
const Details_1 = __importDefault(require("@theme/Details"));
const DiscriminatorTabs_1 = __importDefault(
require("@theme/DiscriminatorTabs")
);
const Markdown_1 = __importDefault(require("@theme/Markdown"));
const SchemaItem_1 = __importDefault(require("@theme/SchemaItem"));
const SchemaTabs_1 = __importDefault(require("@theme/SchemaTabs"));
const TabItem_1 = __importDefault(require("@theme/TabItem"));
// eslint-disable-next-line import/no-extraneous-dependencies
const allof_merge_1 = require("allof-merge");
const clsx_1 = __importDefault(require("clsx"));
const schema_1 = require("docusaurus-plugin-openapi-docs/lib/markdown/schema");
const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
// eslint-disable-next-line import/no-extraneous-dependencies
// const jsonSchemaMergeAllOf = require("json-schema-merge-allof");
const mergeAllOf = (allOf) => {
const onMergeError = (msg) => {
console.warn(msg);
};
const mergedSchemas = (0, allof_merge_1.merge)(allOf, { onMergeError });
return mergedSchemas;
};
// Renders string as markdown, useful for descriptions and qualifiers
const MarkdownWrapper = ({ text }) => {
return react_1.default.createElement(
"div",
{ style: { marginTop: ".5rem", marginBottom: ".5rem" } },
react_1.default.createElement(Markdown_1.default, null, text)
);
};
const Summary = ({ name, schemaName, schema, required }) => {
const { deprecated, nullable } = schema;
const isRequired = Array.isArray(required)
? required.includes(name)
: required === true;
return react_1.default.createElement(
"summary",
null,
react_1.default.createElement(
"span",
{ className: "openapi-schema__container" },
react_1.default.createElement(
"strong",
{
className: (0, clsx_1.default)("openapi-schema__property", {
"openapi-schema__strikethrough": deprecated,
}),
},
name
),
react_1.default.createElement(
"span",
{ className: "openapi-schema__name" },
" ",
schemaName
),
(isRequired || deprecated || nullable) &&
react_1.default.createElement("span", {
className: "openapi-schema__divider",
}),
nullable &&
react_1.default.createElement(
"span",
{ className: "openapi-schema__nullable" },
"nullable"
),
isRequired &&
react_1.default.createElement(
"span",
{ className: "openapi-schema__required" },
"required"
),
deprecated &&
react_1.default.createElement(
"span",
{ className: "openapi-schema__deprecated" },
"deprecated"
)
)
);
};
const AnyOneOf = ({ schema, schemaType }) => {
const type = schema.oneOf ? "oneOf" : "anyOf";
return react_1.default.createElement(
react_1.default.Fragment,
null,
react_1.default.createElement(
"span",
{ className: "badge badge--info", style: { marginBottom: "1rem" } },
type
),
react_1.default.createElement(
SchemaTabs_1.default,
null,
schema[type]?.map((anyOneSchema, index) => {
const label = anyOneSchema.title || anyOneSchema.type;
return (
// @ts-ignore
react_1.default.createElement(
TabItem_1.default,
{ key: index, label: label, value: `${index}-item-properties` },
(isPrimitive(anyOneSchema) || anyOneSchema.const) &&
react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: undefined,
schemaName: anyOneSchema.type,
qualifierMessage: (0, schema_1.getQualifierMessage)(
anyOneSchema
),
schema: anyOneSchema,
discriminator: false,
children: null,
}),
anyOneSchema.type === "object" &&
!anyOneSchema.properties &&
!anyOneSchema.allOf &&
!anyOneSchema.oneOf &&
!anyOneSchema.anyOf &&
react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: undefined,
schemaName: anyOneSchema.type,
qualifierMessage: (0, schema_1.getQualifierMessage)(
anyOneSchema
),
schema: anyOneSchema,
discriminator: false,
children: null,
}),
anyOneSchema.type === "object" &&
anyOneSchema.properties &&
react_1.default.createElement(Properties, {
schema: anyOneSchema,
schemaType: schemaType,
}),
anyOneSchema.allOf &&
react_1.default.createElement(SchemaNode, {
schema: anyOneSchema,
schemaType: schemaType,
}),
anyOneSchema.oneOf &&
react_1.default.createElement(SchemaNode, {
schema: anyOneSchema,
schemaType: schemaType,
}),
anyOneSchema.anyOf &&
react_1.default.createElement(SchemaNode, {
schema: anyOneSchema,
schemaType: schemaType,
}),
anyOneSchema.items &&
react_1.default.createElement(Items, {
schema: anyOneSchema,
schemaType: schemaType,
})
)
);
})
)
);
};
const Properties = ({ schema, schemaType }) => {
const discriminator = schema.discriminator;
if (discriminator && !discriminator.mapping) {
const anyOneOf = schema.oneOf ?? schema.anyOf ?? {};
const inferredMapping = {};
Object.entries(anyOneOf).map(([_, anyOneSchema]) => {
// ensure discriminated property only renders once
if (
schema.properties[discriminator.propertyName] &&
anyOneSchema.properties[discriminator.propertyName]
)
delete anyOneSchema.properties[discriminator.propertyName];
return (inferredMapping[anyOneSchema.title] = anyOneSchema);
});
discriminator["mapping"] = inferredMapping;
}
if (Object.keys(schema.properties).length === 0) {
return react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: "",
required: false,
schemaName: "object",
qualifierMessage: undefined,
schema: {},
});
}
return react_1.default.createElement(
react_1.default.Fragment,
null,
Object.entries(schema.properties).map(([key, val]) =>
react_1.default.createElement(SchemaEdge, {
key: key,
name: key,
schema: val,
required: Array.isArray(schema.required)
? schema.required.includes(key)
: false,
discriminator: discriminator,
schemaType: schemaType,
})
)
);
};
const PropertyDiscriminator = ({
name,
schemaName,
schema,
schemaType,
discriminator,
required,
}) => {
if (!schema) {
return null;
}
return react_1.default.createElement(
react_1.default.Fragment,
null,
react_1.default.createElement(
"div",
{ className: "openapi-discriminator__item openapi-schema__list-item" },
react_1.default.createElement(
"div",
null,
react_1.default.createElement(
"span",
{ className: "openapi-schema__container" },
react_1.default.createElement(
"strong",
{
className: "openapi-discriminator__name openapi-schema__property",
},
name
),
schemaName &&
react_1.default.createElement(
"span",
{ className: "openapi-schema__name" },
" ",
schemaName
),
required &&
react_1.default.createElement("span", {
className: "openapi-schema__divider",
}),
required &&
react_1.default.createElement(
"span",
{ className: "openapi-schema__required" },
"required"
)
),
react_1.default.createElement(
"div",
{ style: { marginLeft: "1rem" } },
schema.description &&
react_1.default.createElement(MarkdownWrapper, {
text: schema.description,
}),
(0, schema_1.getQualifierMessage)(discriminator) &&
react_1.default.createElement(MarkdownWrapper, {
text: (0, schema_1.getQualifierMessage)(discriminator),
})
),
react_1.default.createElement(
DiscriminatorTabs_1.default,
{ className: "openapi-tabs__discriminator" },
Object.keys(discriminator.mapping).map((key, index) =>
// @ts-ignore
react_1.default.createElement(
TabItem_1.default,
{ key: index, label: key, value: `${index}-item-discriminator` },
react_1.default.createElement(SchemaNode, {
schema: discriminator.mapping[key],
schemaType: schemaType,
})
)
)
)
)
),
schema.properties &&
Object.entries(schema.properties).map(
([key, val]) =>
key !== discriminator.propertyName &&
react_1.default.createElement(SchemaEdge, {
key: key,
name: key,
schema: val,
required: Array.isArray(schema.required)
? schema.required.includes(key)
: false,
discriminator: false,
schemaType: schemaType,
})
)
);
};
const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
let discriminatedSchemas = {};
let inferredMapping = {};
// default to empty object if no parent-level properties exist
const discriminatorProperty = schema.properties
? schema.properties[discriminator.propertyName]
: {};
if (schema.allOf) {
const mergedSchemas = mergeAllOf(schema);
if (mergedSchemas.oneOf || mergedSchemas.anyOf) {
discriminatedSchemas = mergedSchemas.oneOf || mergedSchemas.anyOf;
}
} else if (schema.oneOf || schema.anyOf) {
discriminatedSchemas = schema.oneOf || schema.anyOf;
}
// Handle case where no mapping is defined
if (!discriminator.mapping) {
Object.entries(discriminatedSchemas).forEach(([_, subschema], index) => {
inferredMapping[subschema.title ?? `PROP${index}`] = subschema;
});
discriminator.mapping = inferredMapping;
}
// Merge sub schema discriminator property with parent
Object.keys(discriminator.mapping).forEach((key) => {
const subSchema = discriminator.mapping[key];
// Handle discriminated schema with allOf
let mergedSubSchema = {};
if (subSchema.allOf) {
mergedSubSchema = mergeAllOf(subSchema);
}
const subProperties = subSchema.properties || mergedSubSchema.properties;
// Add a safeguard check to avoid referencing subProperties if it's undefined
if (subProperties && subProperties[discriminator.propertyName]) {
if (schema.properties) {
schema.properties[discriminator.propertyName] = {
...schema.properties[discriminator.propertyName],
...subProperties[discriminator.propertyName],
};
if (subSchema.required && !schema.required) {
schema.required = subSchema.required;
}
// Avoid duplicating property
delete subProperties[discriminator.propertyName];
} else {
schema.properties = {};
schema.properties[discriminator.propertyName] =
subProperties[discriminator.propertyName];
// Avoid duplicating property
delete subProperties[discriminator.propertyName];
}
}
});
const name = discriminator.propertyName;
const schemaName = (0, schema_1.getSchemaName)(discriminatorProperty);
// Default case for discriminator without oneOf/anyOf/allOf
return react_1.default.createElement(PropertyDiscriminator, {
name: name,
schemaName: schemaName,
schema: schema,
schemaType: schemaType,
discriminator: discriminator,
required: Array.isArray(schema.required)
? schema.required.includes(name)
: schema.required,
});
};
const AdditionalProperties = ({ schema, schemaType }) => {
const additionalProperties = schema.additionalProperties;
if (!additionalProperties) return null;
// Handle free-form objects
if (
additionalProperties === true ||
(0, isEmpty_1.default)(additionalProperties)
) {
return react_1.default.createElement(SchemaItem_1.default, {
name: "property name*",
required: false,
schemaName: "any",
qualifierMessage: (0, schema_1.getQualifierMessage)(schema),
schema: schema,
collapsible: false,
discriminator: false,
});
}
// Handle objects, arrays, complex schemas
if (
additionalProperties.properties ||
additionalProperties.items ||
additionalProperties.allOf ||
additionalProperties.additionalProperties ||
additionalProperties.oneOf ||
additionalProperties.anyOf
) {
const title =
additionalProperties.title ||
(0, schema_1.getSchemaName)(additionalProperties);
const required = schema.required || false;
return react_1.default.createElement(SchemaNodeDetails, {
name: "property name*",
schemaName: title,
required: required,
nullable: schema.nullable,
schema: additionalProperties,
schemaType: schemaType,
});
}
// Handle primitive types
if (
additionalProperties.type === "string" ||
additionalProperties.type === "boolean" ||
additionalProperties.type === "integer" ||
additionalProperties.type === "number" ||
additionalProperties.type === "object"
) {
const schemaName = (0, schema_1.getSchemaName)(additionalProperties);
return react_1.default.createElement(SchemaItem_1.default, {
name: "property name*",
required: false,
schemaName: schemaName,
qualifierMessage: (0, schema_1.getQualifierMessage)(schema),
schema: additionalProperties,
collapsible: false,
discriminator: false,
children: null,
});
}
// Unknown type
return null;
};
const SchemaNodeDetails = ({
name,
schemaName,
schema,
required,
schemaType,
}) => {
return react_1.default.createElement(
SchemaItem_1.default,
{ collapsible: true },
react_1.default.createElement(
Details_1.default,
{
className: "openapi-markdown__details",
summary: react_1.default.createElement(Summary, {
name: name,
schemaName: schemaName,
schema: schema,
required: required,
}),
},
react_1.default.createElement(
"div",
{ style: { marginLeft: "1rem" } },
schema.description &&
react_1.default.createElement(MarkdownWrapper, {
text: schema.description,
}),
(0, schema_1.getQualifierMessage)(schema) &&
react_1.default.createElement(MarkdownWrapper, {
text: (0, schema_1.getQualifierMessage)(schema),
}),
react_1.default.createElement(SchemaNode, {
schema: schema,
schemaType: schemaType,
})
)
)
);
};
const Items = ({ schema, schemaType }) => {
// Process schema.items to handle allOf merging
let itemsSchema = schema.items;
if (schema.items?.allOf) {
itemsSchema = mergeAllOf(schema.items);
}
// Handle complex schemas with multiple schema types
const hasOneOfAnyOf = itemsSchema?.oneOf || itemsSchema?.anyOf;
const hasProperties = itemsSchema?.properties;
const hasAdditionalProperties = itemsSchema?.additionalProperties;
if (hasOneOfAnyOf || hasProperties || hasAdditionalProperties) {
return react_1.default.createElement(
react_1.default.Fragment,
null,
react_1.default.createElement(ArrayBrackets_1.OpeningArrayBracket, null),
hasOneOfAnyOf &&
react_1.default.createElement(AnyOneOf, {
schema: itemsSchema,
schemaType: schemaType,
}),
hasProperties &&
react_1.default.createElement(Properties, {
schema: itemsSchema,
schemaType: schemaType,
}),
hasAdditionalProperties &&
react_1.default.createElement(AdditionalProperties, {
schema: itemsSchema,
schemaType: schemaType,
}),
react_1.default.createElement(ArrayBrackets_1.ClosingArrayBracket, null)
);
}
// Handles basic types (string, number, integer, boolean, object)
if (
itemsSchema?.type === "string" ||
itemsSchema?.type === "number" ||
itemsSchema?.type === "integer" ||
itemsSchema?.type === "boolean" ||
itemsSchema?.type === "object"
) {
return react_1.default.createElement(
"div",
{ style: { marginLeft: ".5rem" } },
react_1.default.createElement(ArrayBrackets_1.OpeningArrayBracket, null),
react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: "", // No name for array items
schemaName: (0, schema_1.getSchemaName)(itemsSchema),
qualifierMessage: (0, schema_1.getQualifierMessage)(itemsSchema),
schema: itemsSchema,
discriminator: false,
children: null,
}),
react_1.default.createElement(ArrayBrackets_1.ClosingArrayBracket, null)
);
}
// Handles fallback case (use createEdges logic)
return react_1.default.createElement(
react_1.default.Fragment,
null,
react_1.default.createElement(ArrayBrackets_1.OpeningArrayBracket, null),
Object.entries(itemsSchema || {}).map(([key, val]) =>
react_1.default.createElement(SchemaEdge, {
key: key,
name: key,
schema: val,
schemaType: schemaType,
required: Array.isArray(schema.required)
? schema.required.includes(key)
: false,
})
),
react_1.default.createElement(ArrayBrackets_1.ClosingArrayBracket, null)
);
};
const SchemaEdge = ({ name, schema, required, discriminator, schemaType }) => {
if (
(schemaType === "request" && schema.readOnly) ||
(schemaType === "response" && schema.writeOnly)
) {
return null;
}
const schemaName = (0, schema_1.getSchemaName)(schema);
if (discriminator && discriminator.propertyName === name) {
return react_1.default.createElement(PropertyDiscriminator, {
name: name,
schemaName: schemaName,
schema: schema,
schemaType: schemaType,
discriminator: discriminator,
required: required,
});
}
if (schema.oneOf || schema.anyOf) {
// return <AnyOneOf schema={schema} schemaType={schemaType} />;
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: schemaName,
schemaType: schemaType,
required: required,
schema: schema,
nullable: schema.nullable,
});
}
if (schema.properties) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: schemaName,
schemaType: schemaType,
required: required,
schema: schema,
nullable: schema.nullable,
});
}
if (schema.additionalProperties) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: schemaName,
schemaType: schemaType,
required: required,
schema: schema,
nullable: schema.nullable,
});
}
if (schema.items?.properties) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: schemaName,
required: required,
nullable: schema.nullable,
schema: schema,
schemaType: schemaType,
});
}
if (schema.items?.anyOf || schema.items?.oneOf) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: schemaName,
required: required,
nullable: schema.nullable,
schema: schema,
schemaType: schemaType,
});
}
if (schema.allOf) {
// handle circular properties
if (
schema.allOf &&
schema.allOf.length &&
schema.allOf.length === 1 &&
typeof schema.allOf[0] === "string"
) {
return react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: name,
required: Array.isArray(required) ? required.includes(name) : required,
schemaName: schema.allOf[0],
qualifierMessage: undefined,
schema: schema.allOf[0],
discriminator: false,
children: null,
});
}
const mergedSchemas = mergeAllOf(schema);
if (
(schemaType === "request" && mergedSchemas.readOnly) ||
(schemaType === "response" && mergedSchemas.writeOnly)
) {
return null;
}
const mergedSchemaName = (0, schema_1.getSchemaName)(mergedSchemas);
if (mergedSchemas.oneOf || mergedSchemas.anyOf) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: mergedSchemaName,
required: Array.isArray(mergedSchemas.required)
? mergedSchemas.required.includes(name)
: mergedSchemas.required,
nullable: mergedSchemas.nullable,
schema: mergedSchemas,
schemaType: schemaType,
});
}
if (mergedSchemas.properties !== undefined) {
return react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: mergedSchemaName,
required: Array.isArray(mergedSchemas.required)
? mergedSchemas.required.includes(name)
: mergedSchemas.required,
nullable: mergedSchemas.nullable,
schema: mergedSchemas,
schemaType: schemaType,
});
}
if (mergedSchemas.items?.properties) {
react_1.default.createElement(SchemaNodeDetails, {
name: name,
schemaName: mergedSchemaName,
required: Array.isArray(mergedSchemas.required)
? mergedSchemas.required.includes(name)
: mergedSchemas.required,
nullable: mergedSchemas.nullable,
schema: mergedSchemas,
schemaType: schemaType,
});
}
return react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: name,
required: Array.isArray(required) ? required.includes(name) : required,
schemaName: mergedSchemaName,
qualifierMessage: (0, schema_1.getQualifierMessage)(mergedSchemas),
schema: mergedSchemas,
discriminator: false,
children: null,
});
}
return react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: name,
required: Array.isArray(required) ? required.includes(name) : required,
schemaName: schemaName,
qualifierMessage: (0, schema_1.getQualifierMessage)(schema),
schema: schema,
discriminator: false,
children: null,
});
};
function renderChildren(schema, schemaType) {
return react_1.default.createElement(
react_1.default.Fragment,
null,
schema.oneOf &&
react_1.default.createElement(AnyOneOf, {
schema: schema,
schemaType: schemaType,
}),
schema.anyOf &&
react_1.default.createElement(AnyOneOf, {
schema: schema,
schemaType: schemaType,
}),
schema.properties &&
react_1.default.createElement(Properties, {
schema: schema,
schemaType: schemaType,
}),
schema.additionalProperties &&
react_1.default.createElement(AdditionalProperties, {
schema: schema,
schemaType: schemaType,
}),
schema.items &&
react_1.default.createElement(Items, {
schema: schema,
schemaType: schemaType,
})
);
}
const SchemaNode = ({ schema, schemaType }) => {
if (
(schemaType === "request" && schema.readOnly) ||
(schemaType === "response" && schema.writeOnly)
) {
return null;
}
if (schema.discriminator) {
const { discriminator } = schema;
return react_1.default.createElement(DiscriminatorNode, {
discriminator: discriminator,
schema: schema,
schemaType: schemaType,
});
}
// Handle allOf, oneOf, anyOf without discriminators
if (schema.allOf) {
const mergedSchemas = mergeAllOf(schema);
if (
(schemaType === "request" && mergedSchemas.readOnly) ||
(schemaType === "response" && mergedSchemas.writeOnly)
) {
return null;
}
return react_1.default.createElement(
"div",
null,
mergedSchemas.oneOf &&
react_1.default.createElement(AnyOneOf, {
schema: mergedSchemas,
schemaType: schemaType,
}),
mergedSchemas.anyOf &&
react_1.default.createElement(AnyOneOf, {
schema: mergedSchemas,
schemaType: schemaType,
}),
mergedSchemas.properties &&
react_1.default.createElement(Properties, {
schema: mergedSchemas,
schemaType: schemaType,
}),
mergedSchemas.items &&
react_1.default.createElement(Items, {
schema: mergedSchemas,
schemaType: schemaType,
})
);
}
// Handle primitives
if (
schema.type &&
!schema.oneOf &&
!schema.anyOf &&
!schema.properties &&
!schema.allOf &&
!schema.items &&
!schema.additionalProperties
) {
const schemaName = (0, schema_1.getSchemaName)(schema);
return react_1.default.createElement(SchemaItem_1.default, {
collapsible: false,
name: schema.type,
required: Boolean(schema.required),
schemaName: schemaName,
qualifierMessage: (0, schema_1.getQualifierMessage)(schema),
schema: schema,
discriminator: false,
children: null,
});
}
return renderChildren(schema, schemaType);
};
exports.default = SchemaNode;
const PRIMITIVE_TYPES = {
string: true,
number: true,
integer: true,
boolean: true,
null: true,
};
const isPrimitive = (schema) => {
return PRIMITIVE_TYPES[schema.type];
};