fix(config): preserve permission order with Effect decode (#24308)

This commit is contained in:
Kit Langton
2026-04-25 13:30:12 -04:00
committed by GitHub
parent 62651c7114
commit a9740b9133
5 changed files with 146 additions and 90 deletions

View File

@@ -1,10 +1,12 @@
export * as ConfigParse from "./parse"
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
import z from "zod"
import type { DeepMutable } from "@/util/schema"
import { InvalidError, JsonError } from "./error"
type Schema<T> = z.ZodType<T>
type ZodSchema<T> = z.ZodType<T>
export function jsonc(text: string, filepath: string): unknown {
const errors: JsoncParseError[] = []
@@ -33,7 +35,7 @@ export function jsonc(text: string, filepath: string): unknown {
return data
}
export function schema<T>(schema: Schema<T>, data: unknown, source: string): T {
export function schema<T>(schema: ZodSchema<T>, data: unknown, source: string): T {
const parsed = schema.safeParse(data)
if (parsed.success) return parsed.data
@@ -42,3 +44,45 @@ export function schema<T>(schema: Schema<T>, data: unknown, source: string): T {
issues: parsed.error.issues,
})
}
export function effectSchema<S extends EffectSchema.Decoder<unknown, never>>(
schema: S,
data: unknown,
source: string,
): DeepMutable<S["Type"]> {
const extra = topLevelExtraKeys(schema, data)
if (extra.length) {
throw new InvalidError({
path: source,
issues: [
{
code: "unrecognized_keys",
keys: extra,
path: [],
message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`,
} as z.core.$ZodIssue,
],
})
}
const decoded = EffectSchema.decodeUnknownExit(schema)(data, { errors: "all", propertyOrder: "original" })
if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable<S["Type"]>
const error = Cause.squash(decoded.cause)
throw new InvalidError(
{
path: source,
issues: EffectSchema.isSchemaError(error)
? (SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues as z.core.$ZodIssue[])
: ([{ code: "custom", message: String(error), path: [] }] as z.core.$ZodIssue[]),
},
{ cause: error },
)
}
function topLevelExtraKeys(schema: EffectSchema.Top, data: unknown) {
if (typeof data !== "object" || data === null || Array.isArray(data)) return []
if (schema.ast._tag !== "Objects" || schema.ast.indexSignatures.length > 0) return []
const known = new Set(schema.ast.propertySignatures.map((item) => String(item.name)))
return Object.keys(data).filter((key) => !known.has(key))
}