Files
homarr/packages/api/src/open-api-scalar.ts
T
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00

93 lines
3.1 KiB
TypeScript

type RecordValue = Record<string, unknown>;
function asRecord(value: unknown): RecordValue | undefined {
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
return value as RecordValue;
}
function hasExactKeys(value: RecordValue, keys: string[]) {
const actual = Object.keys(value).toSorted();
const expected = keys.toSorted();
if (actual.length !== expected.length) return false;
return actual.every((key, index) => key === expected[index]);
}
function isUnconstrainedRecursiveJsonSchema(name: string, value: unknown) {
const root = asRecord(value);
if (!root || !hasExactKeys(root, ["anyOf"]) || !Array.isArray(root.anyOf)) return false;
if (root.anyOf.length !== 6) return false;
const simpleTypes = new Set<string>();
let hasArray = false;
let hasObject = false;
const self = `#/components/schemas/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`;
for (const member of root.anyOf) {
const variant = asRecord(member);
if (!variant) return false;
if (typeof variant.type === "string" && hasExactKeys(variant, ["type"])) {
simpleTypes.add(variant.type);
continue;
}
if (variant.type === "array" && hasExactKeys(variant, ["type", "items"])) {
const items = asRecord(variant.items);
if (!items || !hasExactKeys(items, ["$ref"]) || items.$ref !== self) return false;
hasArray = true;
continue;
}
if (variant.type === "object" && hasExactKeys(variant, ["type", "propertyNames", "additionalProperties"])) {
const propertyNames = asRecord(variant.propertyNames);
const additionalProperties = asRecord(variant.additionalProperties);
if (!propertyNames || !hasExactKeys(propertyNames, ["type"]) || propertyNames.type !== "string") return false;
if (!additionalProperties || !hasExactKeys(additionalProperties, ["$ref"]) || additionalProperties.$ref !== self)
return false;
hasObject = true;
continue;
}
return false;
}
return (
simpleTypes.size === 4 &&
simpleTypes.has("string") &&
simpleTypes.has("number") &&
simpleTypes.has("boolean") &&
simpleTypes.has("null") &&
hasArray &&
hasObject
);
}
/** Scalar cannot render the generator's unconstrained, self-recursive JSON union. */
export function normalizeRecursiveJsonSchemasForScalar<T extends object>(document: T): T {
const root = asRecord(document);
const components = asRecord(root?.components);
const schemas = asRecord(components?.schemas);
if (!components || !schemas) return document;
const normalizedSchemas = { ...schemas };
let changed = false;
for (const [name, value] of Object.entries(schemas)) {
if (!isUnconstrainedRecursiveJsonSchema(name, value)) continue;
normalizedSchemas[name] = {
anyOf: [
{ type: "string" },
{ type: "number" },
{ type: "boolean" },
{ type: "null" },
{ type: "array" },
{ type: "object" },
],
};
changed = true;
}
if (!changed) return document;
return {
...document,
components: {
...components,
schemas: normalizedSchemas,
},
} as T;
}