feat(effect-zod): translate Schema.check filters into zod .superRefine + promote LSP refinement to Effect layer (#23173)

This commit is contained in:
Kit Langton
2026-04-17 16:46:05 -04:00
committed by GitHub
parent 47f553f9ba
commit e7686dbd64
4 changed files with 190 additions and 19 deletions

View File

@@ -20,24 +20,26 @@ export const Entry = Schema.Union([
}),
]).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)]).pipe(
withStatics((s) => ({
zod: zod(s).refine(
(data) => {
if (typeof data === "boolean") return true
const serverIds = new Set(Object.values(LSPServer).map((server) => server.id))
return Object.entries(data).every(([id, config]) => {
if (config.disabled) return true
if (serverIds.has(id)) return true
return Boolean(config.extensions)
})
},
{
error: "For custom LSP servers, 'extensions' array is required.",
},
),
})),
/**
* For custom (non-builtin) LSP server entries, `extensions` is required so the
* client knows which files the server should attach to. Builtin server IDs and
* explicitly disabled entries are exempt.
*/
export const requiresExtensionsForCustomServers = Schema.makeFilter<boolean | Record<string, Schema.Schema.Type<typeof Entry>>>(
(data) => {
if (typeof data === "boolean") return undefined
const serverIds = new Set(Object.values(LSPServer).map((server) => server.id))
const ok = Object.entries(data).every(([id, config]) => {
if ("disabled" in config && config.disabled) return true
if (serverIds.has(id)) return true
return "extensions" in config && Boolean(config.extensions)
})
return ok ? undefined : "For custom LSP servers, 'extensions' array is required."
},
)
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
.check(requiresExtensionsForCustomServers)
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>

View File

@@ -16,13 +16,34 @@ function walk(ast: SchemaAST.AST): z.ZodTypeAny {
const override = (ast.annotations as any)?.[ZodOverride] as z.ZodTypeAny | undefined
if (override) return override
const out = body(ast)
let out = body(ast)
for (const check of ast.checks ?? []) {
out = applyCheck(out, check, ast)
}
const desc = SchemaAST.resolveDescription(ast)
const ref = SchemaAST.resolveIdentifier(ast)
const next = desc ? out.describe(desc) : out
return ref ? next.meta({ ref }) : next
}
function applyCheck(out: z.ZodTypeAny, check: SchemaAST.Check<any>, ast: SchemaAST.AST): z.ZodTypeAny {
if (check._tag === "FilterGroup") {
return check.checks.reduce((acc, sub) => applyCheck(acc, sub, ast), out)
}
return out.superRefine((value, ctx) => {
const issue = check.run(value, ast, {} as any)
if (!issue) return
const message = issueMessage(issue) ?? (check.annotations as any)?.message ?? "Validation failed"
ctx.addIssue({ code: "custom", message })
})
}
function issueMessage(issue: any): string | undefined {
if (typeof issue?.annotations?.message === "string") return issue.annotations.message
if (typeof issue?.message === "string") return issue.message
return undefined
}
function body(ast: SchemaAST.AST): z.ZodTypeAny {
if (SchemaAST.isOptional(ast)) return opt(ast)