| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Result, Schema, SchemaIssue } from "effect" |
| 3 | import { assertFalse, assertTrue } from "../utils/assert.ts" |
| 4 | |
| 5 | describe("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 | }) |