116 lines
2.9 KiB
JavaScript
116 lines
2.9 KiB
JavaScript
import { z } from "zod";
|
|
|
|
const metaSchema = z
|
|
.object({
|
|
requestId: z.string().min(1),
|
|
traceId: z.string().min(1),
|
|
correlationId: z.string().min(1).optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
export const successEnvelopeSchema = z
|
|
.object({
|
|
success: z.literal(true),
|
|
data: z.unknown(),
|
|
meta: metaSchema,
|
|
})
|
|
.strict();
|
|
|
|
export const failureEnvelopeSchema = z
|
|
.object({
|
|
success: z.literal(false),
|
|
error: z
|
|
.object({
|
|
code: z.string().min(1),
|
|
category: z.string().min(1).optional(),
|
|
message: z.string().optional(),
|
|
retryable: z.boolean().optional(),
|
|
details: z.unknown().optional(),
|
|
})
|
|
.strict(),
|
|
meta: metaSchema,
|
|
})
|
|
.strict();
|
|
|
|
export const responseEnvelopeSchema = z.discriminatedUnion("success", [
|
|
successEnvelopeSchema,
|
|
failureEnvelopeSchema,
|
|
]);
|
|
|
|
const sampleResourceSchema = z
|
|
.object({
|
|
id: z.string().min(1),
|
|
name: z.string().min(1),
|
|
createdAt: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
const payloadSchemas =
|
|
/** @type {Readonly<Record<string, z.ZodType>>} */ (Object.freeze({
|
|
SampleResourceListPayload: z.array(sampleResourceSchema),
|
|
SampleResourcePayload: sampleResourceSchema,
|
|
}));
|
|
|
|
const requestSchemas =
|
|
/** @type {Readonly<Record<string, z.ZodType>>} */ (Object.freeze({
|
|
SampleResourceListQuery: z
|
|
.object({
|
|
cursor: z.string().optional(),
|
|
limit: z.int().min(1).max(100).default(20),
|
|
})
|
|
.strict(),
|
|
CreateSampleResourceCommand: z
|
|
.object({
|
|
name: z.string().trim().min(1).max(120),
|
|
})
|
|
.strict(),
|
|
}));
|
|
|
|
/** @param {unknown} value */
|
|
export function validateEnvelope(value) {
|
|
return projectResult(responseEnvelopeSchema.safeParse(value));
|
|
}
|
|
|
|
/** @param {string} schemaId @param {unknown} value */
|
|
export function validateOperationPayload(schemaId, value) {
|
|
const schema = payloadSchemas[schemaId];
|
|
if (!schema) return missingSchema(schemaId);
|
|
return projectResult(schema.safeParse(value));
|
|
}
|
|
|
|
/** @param {string} schemaId @param {unknown} value */
|
|
export function validateOperationRequest(schemaId, value) {
|
|
const schema = requestSchemas[schemaId];
|
|
if (!schema) return missingSchema(schemaId);
|
|
return projectResult(schema.safeParse(value));
|
|
}
|
|
|
|
/** @param {string} schemaId */
|
|
function missingSchema(schemaId) {
|
|
return {
|
|
success: /** @type {false} */ (false),
|
|
issues: [{ path: "", code: "SCHEMA_NOT_REGISTERED", schemaId }],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {{ success: true, data: unknown } |
|
|
* { success: false, error: { issues: Array<{ path: PropertyKey[], code: string }> } }} result
|
|
*/
|
|
function projectResult(result) {
|
|
if (result.success) {
|
|
return {
|
|
success: /** @type {true} */ (true),
|
|
data: structuredClone(result.data),
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: /** @type {false} */ (false),
|
|
issues: result.error.issues.map((issue) => ({
|
|
path: issue.path.join("."),
|
|
code: issue.code,
|
|
})),
|
|
};
|
|
}
|