feat(effect-zod): translate Schema.withDecodingDefault into zod .default() (#23207)

This commit is contained in:
Kit Langton
2026-04-17 20:55:38 -04:00
committed by GitHub
parent bb90f3bbf9
commit 36119ff173
2 changed files with 129 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { Schema, SchemaGetter } from "effect"
import { Effect, Schema, SchemaGetter } from "effect"
import z from "zod"
import { zod, ZodOverride } from "../../src/util/effect-zod"
@@ -669,4 +669,89 @@ describe("util.effect-zod", () => {
expect(shape.properties.port.exclusiveMinimum).toBe(0)
})
})
describe("Schema.optionalWith defaults", () => {
test("parsing undefined returns the default value", () => {
const schema = zod(
Schema.Struct({
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
}),
)
expect(schema.parse({})).toEqual({ mode: "ctrl-x" })
expect(schema.parse({ mode: undefined })).toEqual({ mode: "ctrl-x" })
})
test("parsing a real value returns that value (default does not fire)", () => {
const schema = zod(
Schema.Struct({
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
}),
)
expect(schema.parse({ mode: "ctrl-y" })).toEqual({ mode: "ctrl-y" })
})
test("default on a number field", () => {
const schema = zod(
Schema.Struct({
count: Schema.Number.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(42))),
}),
)
expect(schema.parse({})).toEqual({ count: 42 })
expect(schema.parse({ count: 7 })).toEqual({ count: 7 })
})
test("multiple defaulted fields inside a struct", () => {
const schema = zod(
Schema.Struct({
leader: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
quit: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-c"))),
inner: Schema.String,
}),
)
expect(schema.parse({ inner: "hi" })).toEqual({
leader: "ctrl-x",
quit: "ctrl-c",
inner: "hi",
})
expect(schema.parse({ leader: "a", quit: "b", inner: "c" })).toEqual({
leader: "a",
quit: "b",
inner: "c",
})
})
test("JSON Schema output includes the default key", () => {
const schema = zod(
Schema.Struct({
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
}),
)
const shape = json(schema) as any
expect(shape.properties.mode.default).toBe("ctrl-x")
})
test("default referencing a computed value resolves when evaluated", () => {
// Simulates `keybinds.ts` style of per-platform defaults: the default is
// produced by an Effect that computes a value at decode time.
const platform = "darwin"
const fallback = platform === "darwin" ? "cmd-k" : "ctrl-k"
const schema = zod(
Schema.Struct({
command_palette: Schema.String.pipe(
Schema.optional,
Schema.withDecodingDefault(Effect.sync(() => fallback)),
),
}),
)
expect(schema.parse({})).toEqual({ command_palette: "cmd-k" })
const shape = json(schema) as any
expect(shape.properties.command_palette.default).toBe("cmd-k")
})
test("plain Schema.optional (no default) still emits .optional() (regression)", () => {
const schema = zod(Schema.Struct({ foo: Schema.optional(Schema.String) }))
expect(schema.parse({})).toEqual({})
expect(schema.parse({ foo: "hi" })).toEqual({ foo: "hi" })
})
})
})