1import { assert, describe, it } from "@effect/vitest"
2import { DateTime, Effect, Schema } from "effect"
3import { Model, VariantSchema } from "effect/schema"
4
5describe("VariantSchema", () => {
6 it("FieldOnly and FieldExcept select fields from key arrays", () => {
7 const Test = VariantSchema.make({
8 variants: ["a", "b", "c"],
9 defaultVariant: "a"
10 })
11 const struct = Test.Struct({
12 common: Schema.String,
13 onlyB: Test.FieldOnly(["b"])(Schema.Number),
14 exceptC: Test.FieldExcept(["c"])(Schema.Boolean)
15 })
16
17 assert.deepStrictEqual(Object.keys(Test.extract(struct, "a").fields), ["common", "exceptC"])
18 assert.deepStrictEqual(Object.keys(Test.extract(struct, "b").fields), ["common", "onlyB", "exceptC"])
19 assert.deepStrictEqual(Object.keys(Test.extract(struct, "c").fields), ["common"])
20 })
21
22 it("Class preserves class and variant schema behavior", () => {
23 const Test = VariantSchema.make({
24 variants: ["a", "b"],
25 defaultVariant: "a"
26 })
27 class User extends Test.Class<User>("User")({
28 id: Test.FieldOnly(["a"])(Schema.Number),
29 name: Schema.String
30 }) {}
31
32 const user = User.make({ id: 1, name: "Alice" })
33
34 assert.isTrue(user instanceof User)
35 assert.deepStrictEqual(user, new User({ id: 1, name: "Alice" }))
36 assert.deepStrictEqual(Schema.decodeSync(User)({ id: 1, name: "Alice" }), user)
37 assert.deepStrictEqual(Schema.decodeSync(User.b)({ name: "Alice" }), { name: "Alice" })
38 assert.deepStrictEqual(Object.keys(User.fields), ["id", "name"])
39 })
40
41 it("includes plain variant structs in the default union", () => {
42 const Test = VariantSchema.make({ variants: ["a", "b"], defaultVariant: "a" })
43 const first = Test.Struct({ value: Schema.String })
44 const second = Test.Struct({ value: Schema.Number })
45 const union = Test.Union([first, second])
46
47 assert.strictEqual(union.members.length, 2)
48 assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: "foo" }), { value: "foo" })
49 assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: 42 }), { value: 42 })
50 })
51
52 it("omits undefined fields accepted by VariantSchema.Struct", () => {
53 const Test = VariantSchema.make({ variants: ["a"], defaultVariant: "a" })
54 const struct = Test.Struct({ value: Schema.String, skipped: undefined })
55
56 assert.deepStrictEqual(Object.keys(Test.extract(struct, "a").fields), ["value"])
57 })
58
59 it("omits undefined fields selected by VariantSchema.Field", () => {
60 const Test = VariantSchema.make({ variants: ["a"], defaultVariant: "a" })
61 const struct = Test.Struct({ value: Schema.String, skipped: Test.Field({ a: undefined }) })
62
63 assert.deepStrictEqual(Object.keys(Test.extract(struct, "a").fields), ["value"])
64 })
65
66 it("does not collide the __default variant with the default-schema cache entry", () => {
67 const Test = VariantSchema.make({ variants: ["a", "__default"], defaultVariant: "a" })
68 const defaultFirst = Test.Struct({
69 value: Test.Field({ a: Schema.String, __default: Schema.Number })
70 })
71
72 Test.extract(defaultFirst, "a")
73
74 assert.strictEqual(Test.extract(defaultFirst, "__default").fields.value, Schema.Number)
75
76 const namedFirst = Test.Struct({
77 value: Test.Field({ a: Schema.String, __default: Schema.Number })
78 })
79
80 Test.extract(namedFirst, "__default")
81
82 assert.strictEqual(Test.extract(namedFirst, "a").fields.value, Schema.String)
83 })
84})
85
86describe("Model", () => {
87 it("preserves classes when extracting the default variant", () => {
88 class Person extends Model.Class<Person>("Person")({
89 name: Schema.String
90 }) {}
91
92 const person = Schema.decodeSync(Model.extract(Person, "select"))({ name: "Alex" })
93
94 assert.isTrue(person instanceof Person)
95 })
96
97 it("FieldOption preserves omitted variants", () => {
98 const field = Model.FieldOption(Model.Field({ select: Schema.String, json: undefined }))
99 assert.strictEqual(field.schemas.json, undefined)
100 })
101
102 it("FieldOnly includes fields only in listed variants", () => {
103 const InsertOnly = Model.Struct({
104 value: Model.FieldOnly(["insert"])(Schema.String)
105 })
106
107 assert.deepStrictEqual(Object.keys(Model.extract(InsertOnly, "insert").fields), ["value"])
108 assert.deepStrictEqual(Object.keys(Model.extract(InsertOnly, "select").fields), [])
109 })
110
111 it("BooleanSqlite encodes database bits and JSON booleans across variants", () => {
112 const User = Model.Struct({
113 active: Model.BooleanSqlite
114 })
115
116 const select = Model.extract(User, "select")
117 const json = Model.extract(User, "json")
118 const encodeSelect = Schema.encodeSync(select)
119 const decodeSelect = Schema.decodeSync(select)
120 const encodeJson = Schema.encodeSync(json)
121 const decodeJson = Schema.decodeSync(json)
122
123 assert.deepStrictEqual(encodeSelect({ active: true }), { active: 1 })
124 assert.deepStrictEqual(decodeSelect({ active: 0 }), { active: false })
125 assert.deepStrictEqual(encodeJson({ active: true }), { active: true })
126 assert.deepStrictEqual(decodeJson({ active: false }), { active: false })
127 })
128
129 it.effect("Overrideable defaults are constructor-only and accept explicit overrides", () =>
130 Effect.gen(function*() {
131 const User = Model.Struct({
132 createdAt: Model.DateTimeInsertFromNumber
133 })
134
135 const insert = Model.extract(User, "insert")
136
137 const now = yield* DateTime.now
138 const user = yield* insert.makeEffect({})
139 assert.deepStrictEqual(user.createdAt, now)
140
141 yield* Schema.encodeEffect(insert)({
142 createdAt: Model.Override(now)
143 })
144
145 const error = yield* Schema.decodeUnknownEffect(insert)({}).pipe(
146 Effect.flip
147 )
148 assert.include(error.message, "createdAt")
149 }))
150})