1import { assert, describe, it } from "@effect/vitest"
2import { Result, Schema, SchemaIssue } from "effect"
3import { assertFalse, assertTrue } from "../utils/assert.ts"
4
5describe("SchemaIssue", () => {
6 const formatIssue = SchemaIssue.makeFormatterDefault()
7
8 it("isIssue", () => {
9 assertTrue(SchemaIssue.isIssue(new SchemaIssue.MissingKey(undefined)))
10 assertFalse(SchemaIssue.isIssue({ "~effect/SchemaIssue/Issue": false }))
11 })
12
13 it("does not expose an actual field", () => {
14 const union = Schema.Union([Schema.String, Schema.Number]).ast
15 const invalidValue = new SchemaIssue.InvalidValue()
16 const issues: ReadonlyArray<SchemaIssue.Issue> = [
17 new SchemaIssue.InvalidType(Schema.String.ast),
18 invalidValue,
19 new SchemaIssue.MissingKey(undefined),
20 new SchemaIssue.UnexpectedKey(Schema.String.ast),
21 new SchemaIssue.Forbidden(undefined),
22 new SchemaIssue.OneOf(union, [Schema.String.ast]),
23 new SchemaIssue.Filter(Schema.isMinLength(1), invalidValue),
24 new SchemaIssue.Encoding(Schema.String.ast, invalidValue),
25 new SchemaIssue.Pointer(["value"], invalidValue),
26 new SchemaIssue.Composite(Schema.String.ast, [invalidValue]),
27 new SchemaIssue.AnyOf(union, [invalidValue])
28 ]
29
30 for (const issue of issues) {
31 assertFalse("actual" in issue)
32 }
33 })
34
35 it("preserves structural metadata", () => {
36 const annotations = { message: "custom message" }
37 const invalidType = new SchemaIssue.InvalidType(Schema.String.ast)
38 const invalidValue = new SchemaIssue.InvalidValue(annotations)
39 const unexpectedKey = new SchemaIssue.UnexpectedKey(Schema.Number.ast)
40 const union = Schema.Union([Schema.String, Schema.Number]).ast
41 const successes = [Schema.String.ast]
42 const oneOf = new SchemaIssue.OneOf(union, successes)
43
44 assert.strictEqual(invalidType.ast, Schema.String.ast)
45 assert.strictEqual(invalidValue.annotations, annotations)
46 assert.strictEqual(unexpectedKey.ast, Schema.Number.ast)
47 assert.strictEqual(oneOf.ast, union)
48 assert.strictEqual(oneOf.successes, successes)
49 })
50
51 it("formats leaf issues with static built-in messages", () => {
52 const union = Schema.Union([Schema.String, Schema.Number]).ast
53 const cases: ReadonlyArray<readonly [SchemaIssue.Leaf, string]> = [
54 [new SchemaIssue.InvalidType(Schema.String.ast), "Expected string"],
55 [new SchemaIssue.InvalidValue(), "Expected a valid value"],
56 [new SchemaIssue.MissingKey(undefined), "Missing key"],
57 [new SchemaIssue.UnexpectedKey(Schema.String.ast), "Expected no excess property"],
58 [new SchemaIssue.Forbidden(undefined), "Forbidden operation"],
59 [new SchemaIssue.OneOf(union, [Schema.String.ast]), "Expected exactly one member to match"]
60 ]
61
62 for (const [issue, expected] of cases) {
63 assert.strictEqual(formatIssue(issue), expected)
64 }
65 })
66
67 it("uses an empty AnyOf when no union candidates apply", () => {
68 const result = Schema.decodeUnknownResult(Schema.Union([Schema.String, Schema.Number]))(null)
69
70 assertTrue(Result.isFailure(result))
71 assertTrue(result.failure.issue._tag === "AnyOf")
72 assert.deepStrictEqual(result.failure.issue.issues, [])
73 })
74})