Skip to content
File

Blob: src/worker/routes/requestSchemas.ts

typescript112 lines
1import { z } from "zod";
2 
3// Route handlers historically treated malformed JSON bodies as empty objects
4// and then let field-specific validation choose the public error. These
5// preprocessors preserve that behavior while moving the structural parsing to
6// zod.
7function objectOrEmpty(value: unknown): unknown {
8 if (typeof value !== "object" || value === null || Array.isArray(value)) {
9 return {};
10 }
11 return value;
12}
13 
14const TrimmedStringSchema = z.preprocess(
15 (value) => (typeof value === "string" ? value.trim() : ""),
16 z.string()
17);
18 
19const StringOrEmptySchema = z.preprocess(
20 (value) => (typeof value === "string" ? value : ""),
21 z.string()
22);
23 
24const LowercaseSlugInputSchema = z.preprocess(
25 (value) => (typeof value === "string" ? value.trim().toLowerCase() : ""),
26 z.string()
27);
28 
29const RepositoryVisibilitySchema = z.enum(["public", "private"]);
30 
31const NullableRepositoryVisibilitySchema = z.preprocess(
32 (value) => (value === "public" || value === "private" ? value : null),
33 RepositoryVisibilitySchema.nullable()
34);
35 
36export const RepositoryCreateRequestSchema = z.preprocess(
37 objectOrEmpty,
38 z.object({
39 namespaceSlug: LowercaseSlugInputSchema,
40 slug: LowercaseSlugInputSchema,
41 visibility: NullableRepositoryVisibilitySchema,
42 })
43);
44 
45export const RepositoryVisibilityRequestSchema = z.preprocess(
46 objectOrEmpty,
47 z.object({
48 visibility: NullableRepositoryVisibilitySchema,
49 })
50);
51 
52const PatGrantLevelSchema = z.enum(["pull", "push"]);
53 
54const NullablePatGrantLevelSchema = z.preprocess(
55 (value) => (value === "pull" || value === "push" ? value : null),
56 PatGrantLevelSchema.nullable()
57);
58 
59const PatNamespaceCreateRequestSchema = z.object({
60 scope: z.literal("namespace"),
61 name: TrimmedStringSchema,
62 namespaceSlug: LowercaseSlugInputSchema,
63 level: NullablePatGrantLevelSchema,
64});
65 
66const PatRepoCreateRequestSchema = z.object({
67 scope: z.literal("repo"),
68 name: TrimmedStringSchema,
69 namespaceSlug: LowercaseSlugInputSchema,
70 repoSlug: LowercaseSlugInputSchema,
71 level: NullablePatGrantLevelSchema,
72});
73 
74export const PatCreateRequestSchema = z.preprocess(
75 objectOrEmpty,
76 z.discriminatedUnion("scope", [PatNamespaceCreateRequestSchema, PatRepoCreateRequestSchema])
77);
78 
79export const AdminCompactionRequestSchema = z.preprocess(
80 objectOrEmpty,
81 z.object({
82 dryRun: z.preprocess(
83 (value) => (value === false ? false : undefined),
84 z.literal(false).optional()
85 ),
86 })
87);
88 
89export const AdminRefsPayloadSchema = z.array(
90 z
91 .object({
92 name: z.string(),
93 oid: z.string(),
94 })
95 .passthrough()
96);
97 
98export const AdminHeadPayloadSchema = z
99 .object({
100 target: z.string(),
101 oid: z.string().optional(),
102 unborn: z.boolean().optional(),
103 })
104 .passthrough();
105 
106export const AdminPurgeRequestSchema = z.preprocess(
107 objectOrEmpty,
108 z.object({
109 confirm: StringOrEmptySchema,
110 })
111);