@beignet/core
Version:
Core framework primitives for Beignet
100 lines (89 loc) • 3.28 kB
text/typescript
/**
* Internal schema shape helpers shared by server route registration and
* OpenAPI generation.
*
* These helpers are intentionally zod-free: zod is an optional peer
* dependency, so shape extraction duck-types Zod's `_def.shape` internals and
* returns undefined for schemas it cannot introspect. This module is not
* exported from the package index.
*/
/**
* Extract the field shape (field name → field schema) from an object schema.
*
* Returns undefined when the schema is not an introspectable object schema,
* for example a non-Zod Standard Schema.
*/
export function getObjectSchemaShape(
schema: unknown,
): Record<string, unknown> | undefined {
if (!schema || typeof schema !== "object") return undefined;
const schemaDef = (schema as Record<string, unknown>)?._def;
if (!schemaDef || typeof schemaDef !== "object") return undefined;
if (!("shape" in schemaDef)) return undefined;
let shape: unknown;
if (typeof schemaDef.shape === "function") {
shape = schemaDef.shape();
} else if (typeof schemaDef.shape === "object") {
shape = schemaDef.shape;
}
if (!shape || typeof shape !== "object") return undefined;
return shape as Record<string, unknown>;
}
/**
* Compare a path template's dynamic keys against a pathParams schema shape.
*/
export function comparePathParamsToTemplate(args: {
pathKeys: readonly string[];
shapeKeys: readonly string[];
}): { missingKeys: string[]; extraKeys: string[] } {
const missingKeys = args.pathKeys.filter(
(key) => !args.shapeKeys.includes(key),
);
const extraKeys = args.shapeKeys.filter(
(key) => !args.pathKeys.includes(key),
);
return { missingKeys, extraKeys };
}
/**
* Format a pathParams/template mismatch for error messages.
*
* Shared so server registration and OpenAPI generation report the same
* diagnostic details.
*/
export function formatPathParamsMismatch(args: {
missingKeys: readonly string[];
extraKeys: readonly string[];
}): string {
return [
args.missingKeys.length > 0
? `missing pathParams keys: ${args.missingKeys.join(", ")}`
: undefined,
args.extraKeys.length > 0
? `extra pathParams keys: ${args.extraKeys.join(", ")}`
: undefined,
]
.filter(Boolean)
.join("; ");
}
/** Compare an introspected query schema's fields with its transport fields. */
export function compareQueryTransportFields(args: {
schemaKeys: readonly string[];
transportKeys: readonly string[];
}): boolean {
const schemaKeys = [...args.schemaKeys].sort();
const transportKeys = [...args.transportKeys].sort();
return (
schemaKeys.length === transportKeys.length &&
schemaKeys.every((key, index) => key === transportKeys[index])
);
}
/** Format a query schema/transport mismatch consistently across consumers. */
export function formatQueryTransportMismatch(args: {
contractName: string;
schemaKeys: readonly string[];
transportKeys: readonly string[];
}): string {
const schemaKeys = [...args.schemaKeys].sort();
const transportKeys = [...args.transportKeys].sort();
return `Query transport fields for contract "${args.contractName}" must match its query schema fields (schema: ${schemaKeys.join(", ") || "none"}; transport: ${transportKeys.join(", ") || "none"}).`;
}