1import { Cause, Exit, HashMap, Option, Predicate, Record, Result, Schema, SchemaTransformation } from "effect"
2import { describe, it } from "vitest"
3import {
4 assertNone,
5 assertSchemaIssueError,
6 assertSome,
7 deepStrictEqual,
8 strictEqual,
9 throws
10} from "../utils/assert.ts"
11
12class Value extends Schema.Class<Value, { readonly brand: unique symbol }>("Value")({
13 a: Schema.Date
14}) {}
15
16function addOne(date: Date): Date {
17 const time = date.getTime()
18 if (time === -1) {
19 return new Date("")
20 }
21 return new Date(time + 1)
22}
23
24function addTwo(date: Date): Date {
25 const time = date.getTime()
26 return new Date(time + 2)
27}
28
29describe("Optic generation", () => {
30 it("overrideToCodecIso", () => {
31 const schema = Schema.URL.pipe(Schema.overrideToCodecIso(Schema.String, SchemaTransformation.urlFromString))
32 const optic = Schema.toIso(schema)
33 const modify = optic.modify((s) => s + "test")
34 deepStrictEqual(modify(new URL("https://example.com")), new URL("https://example.com/test"))
35 })
36
37 describe("toIso", () => {
38 describe("Class", () => {
39 it("Class", () => {
40 class A extends Schema.Class<A>("A")({ value: Value }) {}
41 class B extends Schema.Class<B>("B")({ a: A }) {}
42
43 const schema = B
44 const optic = Schema.toIso(schema).key("a").key("value").key("a")
45 const modify = optic.modify(addOne)
46
47 deepStrictEqual(
48 modify(B.make({ a: A.make({ value: Value.make({ a: new Date(0) }) }) })),
49 B.make({ a: A.make({ value: Value.make({ a: new Date(1) }) }) })
50 )
51 })
52 })
53
54 it("toType(Class)", () => {
55 const schema = Schema.toType(Value)
56 const optic = Schema.toIso(schema).key("a")
57 const modify = optic.modify(addOne)
58
59 deepStrictEqual(modify(Value.make({ a: new Date(0) })), Value.make({ a: new Date(1) }))
60 })
61
62 it("toEncoded(Class)", () => {
63 const schema = Schema.toEncoded(Value)
64 const optic = Schema.toIso(schema).key("a")
65 const modify = optic.modify(addOne)
66
67 deepStrictEqual(modify({ a: new Date(0) }), { a: new Date(1) })
68 })
69
70 describe("brand", () => {
71 it("Number & isPositive", () => {
72 const schema = Schema.Number.check(Schema.isGreaterThan(0)).pipe(Schema.brand("isPositive"))
73 const optic = Schema.toIso(schema)
74 const modify = optic.modify((n) => schema.make(n - 1))
75
76 strictEqual(modify(schema.make(2)), 1)
77 throws(() => modify(schema.make(1)), (error) => {
78 assertSchemaIssueError(error, "Expected a value greater than 0")
79 })
80 })
81 })
82
83 it("Tuple", () => {
84 const schema = Schema.Tuple([Value, Schema.optionalKey(Value)])
85 const optic = Schema.toIso(schema).key("0").key("a")
86 const modify = optic.modify(addOne)
87
88 deepStrictEqual(
89 modify([Value.make({ a: new Date(0) })]),
90 [Value.make({ a: new Date(1) })]
91 )
92 })
93
94 it("Array", () => {
95 const schema = Schema.Array(Value)
96 const optic = Schema.toIso(schema)
97 const item = Schema.toIsoFocus(Value).key("a")
98 const modify = optic.modify((as) => as.map(item.modify(addOne)))
99
100 deepStrictEqual(modify([Value.make({ a: new Date(0) })]), [Value.make({ a: new Date(1) })])
101 })
102
103 it("NonEmptyArray", () => {
104 const schema = Schema.NonEmptyArray(Value)
105 const optic = Schema.toIso(schema)
106 const item = Schema.toIsoFocus(Value).key("a")
107 const modify = optic.modify(([a, ...rest]) => [item.modify(addOne)(a), ...rest.map(item.modify(addTwo))])
108
109 deepStrictEqual(
110 modify([
111 Value.make({ a: new Date(0) }),
112 Value.make({ a: new Date(1) }),
113 Value.make({ a: new Date(2) })
114 ]),
115 [
116 Value.make({ a: new Date(1) }),
117 Value.make({ a: new Date(3) }),
118 Value.make({ a: new Date(4) })
119 ]
120 )
121 })
122
123 it("TupleWithRest", () => {
124 const schema = Schema.TupleWithRest(Schema.Tuple([Value]), [Value])
125 const optic = Schema.toIso(schema)
126 const item = Schema.toIsoFocus(Value).key("a")
127 const modify = optic.modify((
128 [value, ...rest]
129 ) => [item.modify(addOne)(value), ...rest.map((r) => item.modify(addTwo)(r))])
130
131 deepStrictEqual(
132 modify([
133 Value.make({ a: new Date(0) }),
134 Value.make({ a: new Date(1) }),
135 Value.make({ a: new Date(2) })
136 ]),
137 [
138 Value.make({ a: new Date(1) }),
139 Value.make({ a: new Date(3) }),
140 Value.make({ a: new Date(4) })
141 ]
142 )
143 })
144
145 it("Struct", () => {
146 const schema = Schema.Struct({
147 value: Value,
148 optionalValue: Schema.optionalKey(Value)
149 })
150 const optic = Schema.toIso(schema).key("value").key("a")
151 const modify = optic.modify(addOne)
152
153 deepStrictEqual(
154 modify({
155 value: Value.make({ a: new Date(0) })
156 }),
157 {
158 value: Value.make({ a: new Date(1) })
159 }
160 )
161 deepStrictEqual(
162 modify({
163 value: Value.make({ a: new Date(0) }),
164 optionalValue: Value.make({ a: new Date(2) })
165 }),
166 {
167 value: Value.make({ a: new Date(1) }),
168 optionalValue: Value.make({ a: new Date(2) })
169 }
170 )
171 })
172
173 it("Record", () => {
174 const schema = Schema.Record(Schema.String, Value)
175 const optic = Schema.toIso(schema)
176 const item = Schema.toIsoFocus(Value).key("a")
177 const modify = optic.modify((rec) => Record.map(rec, item.modify(addOne)))
178
179 deepStrictEqual(
180 modify({
181 a: Value.make({ a: new Date(0) }),
182 b: Value.make({ a: new Date(1) })
183 }),
184 {
185 a: Value.make({ a: new Date(1) }),
186 b: Value.make({ a: new Date(2) })
187 }
188 )
189 })
190
191 it("StructWithRest", () => {
192 const schema = Schema.StructWithRest(
193 Schema.Struct({ a: Value }),
194 [Schema.Record(Schema.String, Value)]
195 )
196 const optic = Schema.toIso(schema)
197 const item = Schema.toIsoFocus(Value).key("a")
198 const modify = optic.modify(({ a, ...rest }) => ({
199 a: item.modify(addOne)(a),
200 ...Record.map(rest, item.modify(addTwo))
201 }))
202
203 deepStrictEqual(
204 modify({ a: Value.make({ a: new Date(0) }), b: Value.make({ a: new Date(1) }) }),
205 { a: Value.make({ a: new Date(1) }), b: Value.make({ a: new Date(3) }) }
206 )
207 })
208
209 it("Union", () => {
210 const schema = Schema.Union([Schema.String, Value])
211 const optic = Schema.toIso(schema)
212 const item = Schema.toIsoFocus(Value).key("a")
213 const modify = optic.modify((x) => Predicate.isString(x) ? x : item.modify(addOne)(x))
214
215 deepStrictEqual(modify("a"), "a")
216 deepStrictEqual(modify(Value.make({ a: new Date(0) })), Value.make({ a: new Date(1) }))
217 })
218
219 it("suspend", () => {
220 interface A {
221 readonly a: Value
222 readonly as: ReadonlyArray<A>
223 }
224 interface AIso {
225 readonly a: typeof Value["Iso"]
226 readonly as: ReadonlyArray<AIso>
227 }
228 const schema = Schema.Struct({
229 a: Value,
230 as: Schema.Array(Schema.suspend((): Schema.Optic<A, AIso> => schema))
231 })
232 const optic = Schema.toIso(schema)
233 const item = Schema.toIsoFocus(Value).key("a")
234 const f = ({ a, as }: AIso): AIso => ({
235 a: item.modify(addOne)(a),
236 as: as.map(f)
237 })
238 const modify = optic.modify(f)
239
240 deepStrictEqual(
241 modify({ a: Value.make({ a: new Date(0) }), as: [{ a: Value.make({ a: new Date(1) }), as: [] }] }),
242 {
243 a: Value.make({ a: new Date(1) }),
244 as: [{ a: Value.make({ a: new Date(2) }), as: [] }]
245 }
246 )
247 })
248
249 it("flip(schema)", () => {
250 const schema = Schema.flip(Value)
251 const optic = Schema.toIso(schema).key("a")
252 const modify = optic.modify(addOne)
253
254 deepStrictEqual(modify(Value.make({ a: new Date(0) })), { a: new Date(1) })
255 })
256
257 it("flip(flip(schema))", () => {
258 const schema = Schema.flip(Schema.flip(Value))
259 const optic = Schema.toIso(schema).key("a")
260 const modify = optic.modify(addOne)
261
262 deepStrictEqual(modify(Value.make({ a: new Date(0) })), Value.make({ a: new Date(1) }))
263 })
264
265 it("Opaque", () => {
266 class S extends Schema.Opaque<S>()(Schema.Struct({ a: Schema.Date })) {}
267 const schema = S
268 const optic = Schema.toIso(schema).key("a")
269 const modify = optic.modify(addOne)
270
271 deepStrictEqual(modify({ a: new Date(0) }), { a: new Date(1) })
272 })
273
274 it("Option", () => {
275 const schema = Schema.Option(Value)
276 const optic = Schema.toIso(schema).tag("Some").key("value").key("a")
277 const modify = optic.modify(addOne)
278
279 assertSome(
280 modify(Option.some(Value.make({ a: new Date(0) }))),
281 Value.make({ a: new Date(1) })
282 )
283 assertNone(modify(Option.none()))
284 })
285
286 it("Result", () => {
287 const schema = Schema.Result(Value, Value)
288 const optic = Schema.toIso(schema).tag("Success").key("success").key("a")
289 const modify = optic.modify(addOne)
290
291 deepStrictEqual(
292 modify(Result.succeed(Value.make({ a: new Date(0) }))),
293 Result.succeed(Value.make({ a: new Date(1) }))
294 )
295 })
296
297 it("CauseReason", () => {
298 const schema = Schema.CauseReason(Value, Schema.Defect())
299 const optic = Schema.toIso(schema).tag("Fail").key("error").key("a")
300 const modify = optic.modify(addOne)
301
302 deepStrictEqual(
303 modify(Cause.makeFailReason(Value.make({ a: new Date(0) }))),
304 Cause.makeFailReason(Value.make({ a: new Date(1) }))
305 )
306 })
307
308 it("Cause", () => {
309 const schema = Schema.Cause(Value, Value)
310 const optic = Schema.toIso(schema)
311 const failure = Schema.toIsoFocus(Schema.CauseReason(Value, Value)).tag("Fail").key("error").key("a")
312 const modify = optic.modify((failures) => failures.map(failure.modify(addOne)))
313
314 deepStrictEqual(
315 modify(Cause.fail(Value.make({ a: new Date(0) }))),
316 Cause.fail(Value.make({ a: new Date(1) }))
317 )
318 })
319
320 it("Error", () => {
321 const schema = Schema.ErrorInstance()
322 const optic = Schema.toIso(schema)
323 const modify = optic.modify((e) => new Error(e.message + "!"))
324
325 deepStrictEqual(modify(new Error("a")), new Error("a!"))
326 })
327
328 it("Exit", () => {
329 const schema = Schema.Exit(Value, Schema.ErrorInstance(), Schema.Defect())
330 const optic = Schema.toIso(schema).tag("Success").key("value").key("a")
331 const modify = optic.modify(addOne)
332
333 deepStrictEqual(
334 modify(Exit.succeed(Value.make({ a: new Date(0) }))),
335 Exit.succeed(Value.make({ a: new Date(1) }))
336 )
337 })
338
339 it("ReadonlySet", () => {
340 const schema = Schema.ReadonlySet(Value)
341 const optic = Schema.toIso(schema)
342 const item = Schema.toIsoFocus(Value).key("a")
343 const modify = optic.modify((as) => as.map(item.modify(addOne)))
344
345 deepStrictEqual(
346 modify(new Set([Value.make({ a: new Date(0) })])),
347 new Set([Value.make({ a: new Date(1) })])
348 )
349 })
350
351 it("ReadonlyMap", () => {
352 const schema = Schema.ReadonlyMap(Schema.String, Value)
353 const optic = Schema.toIso(schema)
354 const entry = Schema.toIsoFocus(Schema.Tuple([Schema.String, Value])).key("1").key("a")
355 const modify = optic.modify((entries) => entries.map(([key, value]) => entry.modify(addOne)([key, value])))
356
357 deepStrictEqual(
358 modify(new Map([["a", Value.make({ a: new Date(0) })]])),
359 new Map([["a", Value.make({ a: new Date(1) })]])
360 )
361 })
362
363 it("HashMap", () => {
364 const schema = Schema.HashMap(Schema.String, Value)
365 const optic = Schema.toIso(schema)
366 const entry = Schema.toIsoFocus(Schema.Tuple([Schema.String, Value])).key("1").key("a")
367 const modify = optic.modify((entries) => entries.map(([key, value]) => entry.modify(addOne)([key, value])))
368
369 deepStrictEqual(
370 HashMap.toEntries(modify(HashMap.make(["a", Value.make({ a: new Date(0) })]))),
371 HashMap.toEntries(HashMap.make(["a", Value.make({ a: new Date(1) })]))
372 )
373 })
374 })
375})