| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Brand, Schema, SchemaRepresentation } from "effect" |
| 3 | import { throws } from "../../utils/assert.ts" |
| 4 | |
| 5 | const filterId = "acme/schema/minLength" |
| 6 | |
| 7 | const minLengthReviver: SchemaRepresentation.FilterReviver<{ readonly minimum: number }> = { |
| 8 | id: filterId, |
| 9 | payloadSchema: Schema.Struct({ minimum: Schema.Number }), |
| 10 | revive: ({ annotations, payload }) => minLengthCheck(payload.minimum, annotations) |
| 11 | } |
| 12 | |
| 13 | function minLengthCheck(minimum: number, annotations?: Schema.Annotations.Filter) { |
| 14 | return Schema.makeFilter<string>((value) => value.length >= minimum, { |
| 15 | representation: { id: filterId, payload: { minimum } }, |
| 16 | ...annotations |
| 17 | }) |
| 18 | } |
| 19 | |
| 20 | function revive( |
| 21 | schema: Schema.Top, |
| 22 | revivers: ReadonlyArray<SchemaRepresentation.AnyReviver> = [] |
| 23 | ): Schema.Top { |
| 24 | return SchemaRepresentation.fromRepresentation( |
| 25 | SchemaRepresentation.fromJson(SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(schema.ast))), |
| 26 | { revivers } |
| 27 | ) |
| 28 | } |
| 29 | |
| 30 | function assertRepresentationRoundtrip( |
| 31 | schema: Schema.Top, |
| 32 | revivers: ReadonlyArray<SchemaRepresentation.AnyReviver> = [] |
| 33 | ): Schema.Top { |
| 34 | const expected = SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(schema.ast)) |
| 35 | const revived = SchemaRepresentation.fromRepresentation(SchemaRepresentation.fromJson(expected), { revivers }) |
| 36 | assert.deepStrictEqual(SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(revived.ast)), expected) |
| 37 | return revived |
| 38 | } |
| 39 | |
| 40 | function errorFrom(run: () => unknown): Error { |
| 41 | let result: Error | undefined |
| 42 | throws(run, (error: unknown) => { |
| 43 | assert.instanceOf(error, Error) |
| 44 | result = error |
| 45 | return undefined |
| 46 | }) |
| 47 | assert.isDefined(result) |
| 48 | return result |
| 49 | } |
| 50 | |
| 51 | function filterJson(): Schema.Json { |
| 52 | return SchemaRepresentation.toJson( |
| 53 | SchemaRepresentation.toRepresentation(Schema.String.check(minLengthCheck(2)).ast) |
| 54 | ) |
| 55 | } |
| 56 | |
| 57 | describe("SchemaRepresentation.fromRepresentation", () => { |
| 58 | it("revives Null", () => { |
| 59 | assertRepresentationRoundtrip(Schema.Null) |
| 60 | }) |
| 61 | |
| 62 | it("revives Undefined", () => { |
| 63 | assertRepresentationRoundtrip(Schema.Undefined) |
| 64 | }) |
| 65 | |
| 66 | it("revives Void", () => { |
| 67 | assertRepresentationRoundtrip(Schema.Void) |
| 68 | }) |
| 69 | |
| 70 | it("revives Never", () => { |
| 71 | assertRepresentationRoundtrip(Schema.Never) |
| 72 | }) |
| 73 | |
| 74 | it("revives Unknown", () => { |
| 75 | assertRepresentationRoundtrip(Schema.Unknown) |
| 76 | }) |
| 77 | |
| 78 | it("revives Any", () => { |
| 79 | assertRepresentationRoundtrip(Schema.Any) |
| 80 | }) |
| 81 | |
| 82 | it("revives String", () => { |
| 83 | assertRepresentationRoundtrip(Schema.String) |
| 84 | }) |
| 85 | |
| 86 | it("revives Number", () => { |
| 87 | assertRepresentationRoundtrip(Schema.Number) |
| 88 | }) |
| 89 | |
| 90 | it("revives Boolean", () => { |
| 91 | assertRepresentationRoundtrip(Schema.Boolean) |
| 92 | }) |
| 93 | |
| 94 | it("revives BigInt", () => { |
| 95 | assertRepresentationRoundtrip(Schema.BigInt) |
| 96 | }) |
| 97 | |
| 98 | it("revives Symbol", () => { |
| 99 | assertRepresentationRoundtrip(Schema.Symbol) |
| 100 | }) |
| 101 | |
| 102 | it("revives ObjectKeyword", () => { |
| 103 | assertRepresentationRoundtrip(Schema.ObjectKeyword) |
| 104 | }) |
| 105 | |
| 106 | it("revives Literal without changing its type", () => { |
| 107 | for ( |
| 108 | const [literal, differentType] of [ |
| 109 | ["1", 1n], |
| 110 | ["true", true], |
| 111 | [1, "1"], |
| 112 | [1n, "1"], |
| 113 | [true, "true"] |
| 114 | ] as const |
| 115 | ) { |
| 116 | const schema = assertRepresentationRoundtrip(Schema.Literal(literal)) |
| 117 | assert.isTrue(Schema.is(schema)(literal)) |
| 118 | assert.isFalse(Schema.is(schema)(differentType)) |
| 119 | } |
| 120 | }) |
| 121 | |
| 122 | it("revives UniqueSymbol", () => { |
| 123 | const symbol = Symbol.for("acme/schema/symbol") |
| 124 | const schema = assertRepresentationRoundtrip(Schema.UniqueSymbol(symbol)) |
| 125 | assert.isTrue(Schema.is(schema)(symbol)) |
| 126 | assert.isFalse(Schema.is(schema)(Symbol.for("acme/schema/other"))) |
| 127 | }) |
| 128 | |
| 129 | it("revives Enum", () => { |
| 130 | const schema = assertRepresentationRoundtrip(Schema.Enum({ A: "a", One: 1 })) |
| 131 | assert.isTrue(Schema.is(schema)("a")) |
| 132 | assert.isTrue(Schema.is(schema)(1)) |
| 133 | assert.isFalse(Schema.is(schema)("other")) |
| 134 | }) |
| 135 | |
| 136 | it("rejects non-finite numeric Enum values", () => { |
| 137 | throws( |
| 138 | () => |
| 139 | SchemaRepresentation.fromRepresentation({ |
| 140 | representation: { |
| 141 | _tag: "Enum", |
| 142 | enums: [["NumberNaN", Number.NaN]], |
| 143 | checks: [] |
| 144 | }, |
| 145 | references: {} |
| 146 | }, { revivers: [] }), |
| 147 | new Error("A numeric enum value must be finite, got NaN") |
| 148 | ) |
| 149 | }) |
| 150 | |
| 151 | it("revives TemplateLiteral", () => { |
| 152 | const schema = assertRepresentationRoundtrip(Schema.TemplateLiteral(["prefix-", Schema.String])) |
| 153 | assert.isTrue(Schema.is(schema)("prefix-value")) |
| 154 | assert.isFalse(Schema.is(schema)("value")) |
| 155 | }) |
| 156 | |
| 157 | it("revives a reference in TemplateLiteral as a concrete part", () => { |
| 158 | const schema = assertRepresentationRoundtrip( |
| 159 | Schema.TemplateLiteral(["prefix-", Schema.String.annotate({ identifier: "Part" })]) |
| 160 | ) |
| 161 | assert.strictEqual(schema.ast._tag, "TemplateLiteral") |
| 162 | if (schema.ast._tag === "TemplateLiteral") { |
| 163 | assert.strictEqual(schema.ast.parts[1]._tag, "String") |
| 164 | } |
| 165 | assert.isTrue(Schema.is(schema)("prefix-value")) |
| 166 | }) |
| 167 | |
| 168 | it("revives nested references in a TemplateLiteral union", () => { |
| 169 | const schema = assertRepresentationRoundtrip( |
| 170 | Schema.TemplateLiteral([ |
| 171 | "prefix-", |
| 172 | Schema.Union([ |
| 173 | Schema.Literal("a").annotate({ identifier: "A" }), |
| 174 | Schema.Literal("b").annotate({ identifier: "B" }) |
| 175 | ]).annotate({ identifier: "Part" }) |
| 176 | ]) |
| 177 | ) |
| 178 | assert.isTrue(Schema.is(schema)("prefix-a")) |
| 179 | assert.isTrue(Schema.is(schema)("prefix-b")) |
| 180 | assert.isFalse(Schema.is(schema)("prefix-c")) |
| 181 | }) |
| 182 | |
| 183 | it("revives Tuple", () => { |
| 184 | assertRepresentationRoundtrip(Schema.Tuple([Schema.String, Schema.optionalKey(Schema.Number)])) |
| 185 | }) |
| 186 | |
| 187 | it("revives Array", () => { |
| 188 | assertRepresentationRoundtrip(Schema.Array(Schema.String)) |
| 189 | }) |
| 190 | |
| 191 | it("revives TupleWithRest", () => { |
| 192 | assertRepresentationRoundtrip( |
| 193 | Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number, Schema.Boolean]) |
| 194 | ) |
| 195 | }) |
| 196 | |
| 197 | it("revives Struct", () => { |
| 198 | assertRepresentationRoundtrip(Schema.Struct({ |
| 199 | required: Schema.String, |
| 200 | optional: Schema.optionalKey(Schema.Number), |
| 201 | mutable: Schema.mutableKey(Schema.Boolean) |
| 202 | })) |
| 203 | }) |
| 204 | |
| 205 | it("revives Record", () => { |
| 206 | assertRepresentationRoundtrip(Schema.Record(Schema.String, Schema.Number)) |
| 207 | }) |
| 208 | |
| 209 | it("revives a reference used as a Record key", () => { |
| 210 | const schema = assertRepresentationRoundtrip( |
| 211 | Schema.Record(Schema.String.annotate({ identifier: "Key" }), Schema.Number) |
| 212 | ) |
| 213 | assert.strictEqual(schema.ast._tag, "Objects") |
| 214 | if (schema.ast._tag === "Objects") { |
| 215 | assert.strictEqual(schema.ast.indexSignatures[0].parameter._tag, "String") |
| 216 | } |
| 217 | assert.deepStrictEqual(Schema.decodeUnknownSync(schema as Schema.Codec<unknown>)({ a: 1 }), { a: 1 }) |
| 218 | }) |
| 219 | |
| 220 | it("revives StructWithRest", () => { |
| 221 | assertRepresentationRoundtrip( |
| 222 | Schema.StructWithRest(Schema.Struct({ value: Schema.Number }), [Schema.Record(Schema.Symbol, Schema.String)]) |
| 223 | ) |
| 224 | }) |
| 225 | |
| 226 | it("revives Union", () => { |
| 227 | const schema = assertRepresentationRoundtrip(Schema.Union([Schema.String, Schema.Number])) |
| 228 | assert.isTrue(Schema.is(schema)("value")) |
| 229 | assert.isTrue(Schema.is(schema)(1)) |
| 230 | assert.isFalse(Schema.is(schema)(true)) |
| 231 | }) |
| 232 | |
| 233 | it("revives Union options through live and JSON representation roundtrips", () => { |
| 234 | const representation = { |
| 235 | _tag: "Union", |
| 236 | types: [{ _tag: "String", checks: [] }, { _tag: "Number", checks: [] }], |
| 237 | options: { mode: "oneOf" }, |
| 238 | checks: [] |
| 239 | } as const satisfies SchemaRepresentation.Representation |
| 240 | const document = { representation, references: {} } |
| 241 | const revived = SchemaRepresentation.fromRepresentation(document, { revivers: [] }) |
| 242 | assert.strictEqual(revived.ast._tag, "Union") |
| 243 | if (revived.ast._tag === "Union") assert.deepStrictEqual(revived.ast.options, representation.options) |
| 244 | const fromJson = SchemaRepresentation.fromRepresentation( |
| 245 | SchemaRepresentation.fromJson(SchemaRepresentation.toJson(document)), |
| 246 | { revivers: [] } |
| 247 | ) |
| 248 | assert.strictEqual(fromJson.ast._tag, "Union") |
| 249 | if (fromJson.ast._tag === "Union") assert.deepStrictEqual(fromJson.ast.options, representation.options) |
| 250 | }) |
| 251 | |
| 252 | it("revives an empty Union as Never", () => { |
| 253 | const schema = SchemaRepresentation.fromRepresentation({ |
| 254 | representation: { _tag: "Union", types: [], options: { mode: "anyOf" }, checks: [] }, |
| 255 | references: {} |
| 256 | }, { revivers: [] }) |
| 257 | assert.isFalse(Schema.is(schema)(undefined)) |
| 258 | assert.isFalse(Schema.is(schema)(null)) |
| 259 | }) |
| 260 | |
| 261 | it("revives Suspend", () => { |
| 262 | interface Category { |
| 263 | readonly name: string |
| 264 | readonly children: ReadonlyArray<Category> |
| 265 | } |
| 266 | const Category: Schema.Codec<Category> = Schema.Struct({ |
| 267 | name: Schema.String, |
| 268 | children: Schema.Array(Schema.suspend((): Schema.Codec<Category> => Category)) |
| 269 | }).annotate({ identifier: "Category" }) |
| 270 | const schema = revive(Category) as Schema.Codec<unknown> |
| 271 | assert.strictEqual(schema.ast._tag, "Objects") |
| 272 | if (schema.ast._tag === "Objects") { |
| 273 | const children = schema.ast.propertySignatures.find((property) => property.name === "children") |
| 274 | assert.isDefined(children) |
| 275 | assert.strictEqual(children.type._tag, "Arrays") |
| 276 | if (children.type._tag === "Arrays") { |
| 277 | assert.strictEqual(children.type.rest[0]._tag, "Suspend") |
| 278 | } |
| 279 | } |
| 280 | assert.deepStrictEqual( |
| 281 | Schema.decodeUnknownSync(schema)({ |
| 282 | name: "root", |
| 283 | children: [{ name: "child", children: [] }] |
| 284 | }), |
| 285 | { |
| 286 | name: "root", |
| 287 | children: [{ name: "child", children: [] }] |
| 288 | } |
| 289 | ) |
| 290 | assert.strictEqual(SchemaRepresentation.toRepresentation(schema.ast).representation._tag, "Reference") |
| 291 | }) |
| 292 | |
| 293 | it("restores node annotations", () => { |
| 294 | const schema = revive(Schema.String.annotate({ title: "Name" })) |
| 295 | assert.strictEqual(schema.ast.annotations?.title, "Name") |
| 296 | }) |
| 297 | |
| 298 | it("restores node annotations before checks", () => { |
| 299 | const schema = assertRepresentationRoundtrip( |
| 300 | Schema.String |
| 301 | .annotate({ title: "node" }) |
| 302 | .check(minLengthCheck(2, { description: "check" })), |
| 303 | [minLengthReviver] |
| 304 | ) |
| 305 | |
| 306 | assert.strictEqual(schema.ast.annotations?.title, "node") |
| 307 | assert.strictEqual(schema.ast.checks?.[0].annotations?.description, "check") |
| 308 | assert.strictEqual(schema.ast.checks?.[0].annotations?.title, undefined) |
| 309 | }) |
| 310 | |
| 311 | it("restores tuple element annotations", () => { |
| 312 | const schema = revive(Schema.Tuple([Schema.String.annotateKey({ description: "element" })])) |
| 313 | const representation = SchemaRepresentation.toRepresentation(schema.ast).representation |
| 314 | assert.strictEqual(representation._tag, "Arrays") |
| 315 | if (representation._tag === "Arrays") { |
| 316 | assert.strictEqual(representation.elements[0].annotations?.description, "element") |
| 317 | } |
| 318 | }) |
| 319 | |
| 320 | it("restores property annotations", () => { |
| 321 | const schema = revive(Schema.Struct({ value: Schema.String.annotateKey({ description: "property" }) })) |
| 322 | const representation = SchemaRepresentation.toRepresentation(schema.ast).representation |
| 323 | assert.strictEqual(representation._tag, "Objects") |
| 324 | if (representation._tag === "Objects") { |
| 325 | assert.strictEqual(representation.propertySignatures[0].annotations?.description, "property") |
| 326 | } |
| 327 | }) |
| 328 | |
| 329 | it("restores checks added by fromBrand", () => { |
| 330 | type Int = number & Brand.Brand<"Int"> |
| 331 | const Int = Brand.check<Int>(Schema.isInt()) |
| 332 | type Positive = number & Brand.Brand<"Positive"> |
| 333 | const Positive = Brand.check<Positive>(Schema.isGreaterThan(0)) |
| 334 | const schema = Schema.Number.pipe( |
| 335 | Schema.fromBrand("Int", Int), |
| 336 | Schema.fromBrand("Positive", Positive) |
| 337 | ) |
| 338 | |
| 339 | const restored = assertRepresentationRoundtrip(schema, [ |
| 340 | SchemaRepresentation.isIntReviver, |
| 341 | SchemaRepresentation.isGreaterThanReviver |
| 342 | ]) |
| 343 | assert.isTrue(Schema.is(restored)(1)) |
| 344 | assert.isFalse(Schema.is(restored)(1.2)) |
| 345 | assert.isFalse(Schema.is(restored)(-1)) |
| 346 | }) |
| 347 | |
| 348 | it("restores a node representation annotation without schema dependencies", () => { |
| 349 | assertRepresentationRoundtrip(Schema.String.annotate({ |
| 350 | representation: { id: "acme/schema/String", payload: null } |
| 351 | })) |
| 352 | }) |
| 353 | |
| 354 | it("restores a node representation annotation with schema dependencies", () => { |
| 355 | assertRepresentationRoundtrip(Schema.String.annotate({ |
| 356 | representation: { |
| 357 | id: "acme/schema/String", |
| 358 | payload: null, |
| 359 | schemas: [Schema.Number.ast] |
| 360 | } |
| 361 | })) |
| 362 | }) |
| 363 | |
| 364 | it("revives a Filter", () => { |
| 365 | const schema = assertRepresentationRoundtrip( |
| 366 | Schema.String.check(minLengthCheck(2, { description: "at least two" }).abort()), |
| 367 | [minLengthReviver] |
| 368 | ) |
| 369 | assert.strictEqual(Schema.decodeUnknownResult(schema as Schema.Codec<unknown>)("a")._tag, "Failure") |
| 370 | assert.strictEqual(schema.ast.checks?.[0]._tag, "Filter") |
| 371 | assert.isTrue(schema.ast.checks?.[0]._tag === "Filter" && schema.ast.checks[0].aborted) |
| 372 | assert.strictEqual(schema.ast.checks?.[0].annotations?.description, "at least two") |
| 373 | }) |
| 374 | |
| 375 | it("revives a FilterGroup without an identity from its children", () => { |
| 376 | const group = Schema.makeFilterGroup([minLengthCheck(2), minLengthCheck(3)], { description: "both" }) |
| 377 | const schema = assertRepresentationRoundtrip(Schema.String.check(group), [minLengthReviver]) |
| 378 | assert.strictEqual(Schema.decodeUnknownResult(schema as Schema.Codec<unknown>)("ab")._tag, "Failure") |
| 379 | assert.strictEqual(schema.ast.checks?.[0].annotations?.description, "both") |
| 380 | }) |
| 381 | |
| 382 | it("uses an identified FilterGroup reviver instead of its persisted children", () => { |
| 383 | const groupId = "acme/schema/group" |
| 384 | const document = SchemaRepresentation.fromJson({ |
| 385 | representation: { |
| 386 | _tag: "String", |
| 387 | checks: [{ |
| 388 | _tag: "FilterGroup", |
| 389 | representation: { id: groupId, payload: null }, |
| 390 | checks: [{ |
| 391 | _tag: "Filter", |
| 392 | representation: { id: filterId, payload: { minimum: 1 } }, |
| 393 | aborted: false |
| 394 | }] |
| 395 | }] |
| 396 | }, |
| 397 | references: {} |
| 398 | }) |
| 399 | const reviver: SchemaRepresentation.FilterGroupReviver<null> = { |
| 400 | id: groupId, |
| 401 | payloadSchema: Schema.Null, |
| 402 | revive: () => Schema.makeFilterGroup([Schema.makeFilter<string>((value) => value !== "blocked")]) |
| 403 | } |
| 404 | const schema = SchemaRepresentation.fromRepresentation(document, { revivers: [reviver] }) as Schema.Codec<unknown> |
| 405 | assert.strictEqual(Schema.decodeUnknownSync(schema)("allowed"), "allowed") |
| 406 | assert.strictEqual(Schema.decodeUnknownResult(schema)("blocked")._tag, "Failure") |
| 407 | }) |
| 408 | |
| 409 | it("revives a Declaration", () => { |
| 410 | const id = "acme/schema/Box" |
| 411 | const Box = Schema.declare<{ readonly value: string }>( |
| 412 | (input): input is { readonly value: string } => |
| 413 | typeof input === "object" && input !== null && typeof (input as any).value === "string", |
| 414 | { representation: { id, payload: { label: "Box" } } } |
| 415 | ) |
| 416 | const reviver: SchemaRepresentation.DeclarationReviver<{ readonly label: string }> = { |
| 417 | id, |
| 418 | payloadSchema: Schema.Struct({ label: Schema.String }), |
| 419 | revive: ({ annotations, payload }) => |
| 420 | Schema.declare<{ readonly value: string }>( |
| 421 | (input): input is { readonly value: string } => |
| 422 | typeof input === "object" && input !== null && typeof (input as any).value === "string", |
| 423 | { ...annotations, representation: { id, payload } } |
| 424 | ) |
| 425 | } |
| 426 | const schema = assertRepresentationRoundtrip(Box, [reviver]) as Schema.Codec<unknown> |
| 427 | assert.deepStrictEqual(Schema.decodeUnknownSync(schema)({ value: "ok" }), { value: "ok" }) |
| 428 | }) |
| 429 | |
| 430 | it("reports a missing reviver", () => { |
| 431 | assert.strictEqual( |
| 432 | errorFrom(() => |
| 433 | SchemaRepresentation.fromRepresentation(SchemaRepresentation.fromJson(filterJson()), { revivers: [] }) |
| 434 | ) |
| 435 | .message, |
| 436 | `Missing reviver for ${filterId}\n at ["representation"]["checks"][0]["representation"]` |
| 437 | ) |
| 438 | }) |
| 439 | |
| 440 | it("rejects duplicate reviver IDs", () => { |
| 441 | assert.strictEqual( |
| 442 | errorFrom(() => |
| 443 | SchemaRepresentation.fromRepresentation({ |
| 444 | representation: { _tag: "String", checks: [] }, |
| 445 | references: {} |
| 446 | }, { revivers: [minLengthReviver, minLengthReviver] }) |
| 447 | ).message, |
| 448 | `Duplicate reviver for ${filterId}\n at ["revivers"][1]["id"]` |
| 449 | ) |
| 450 | }) |
| 451 | |
| 452 | it("rejects an invalid reviver payload", () => { |
| 453 | const json = filterJson() as any |
| 454 | json.representation.checks[0].representation.payload = { minimum: "two" } |
| 455 | assert.strictEqual( |
| 456 | errorFrom(() => |
| 457 | SchemaRepresentation.fromRepresentation(SchemaRepresentation.fromJson(json), { |
| 458 | revivers: [minLengthReviver] |
| 459 | }) |
| 460 | ).message, |
| 461 | `Invalid representation payload for ${filterId}\n at ["representation"]["checks"][0]["representation"]["payload"]` |
| 462 | ) |
| 463 | }) |
| 464 | |
| 465 | it("preserves a reviver exception by identity", () => { |
| 466 | const cause = new Error("boom") |
| 467 | const reviver = { |
| 468 | ...minLengthReviver, |
| 469 | revive: () => { |
| 470 | throw cause |
| 471 | } |
| 472 | } |
| 473 | assert.strictEqual( |
| 474 | errorFrom(() => |
| 475 | SchemaRepresentation.fromRepresentation(SchemaRepresentation.fromJson(filterJson()), { |
| 476 | revivers: [reviver] |
| 477 | }) |
| 478 | ), |
| 479 | cause |
| 480 | ) |
| 481 | }) |
| 482 | |
| 483 | it("requires a representation identity on a Filter", () => { |
| 484 | assert.strictEqual( |
| 485 | errorFrom(() => |
| 486 | SchemaRepresentation.fromRepresentation( |
| 487 | { |
| 488 | representation: { _tag: "String", checks: [{ _tag: "Filter", aborted: false }] }, |
| 489 | references: {} |
| 490 | }, |
| 491 | { revivers: [] } |
| 492 | ) |
| 493 | ).message, |
| 494 | `Missing representation annotation\n at ["representation"]["checks"][0]["representation"]` |
| 495 | ) |
| 496 | }) |
| 497 | |
| 498 | it("requires a representation identity on a Declaration", () => { |
| 499 | assert.strictEqual( |
| 500 | errorFrom(() => |
| 501 | SchemaRepresentation.fromRepresentation( |
| 502 | { |
| 503 | representation: { _tag: "Declaration", typeParameters: [], checks: [] }, |
| 504 | references: {} |
| 505 | }, |
| 506 | { revivers: [] } |
| 507 | ) |
| 508 | ).message, |
| 509 | `Missing representation annotation\n at ["representation"]["representation"]` |
| 510 | ) |
| 511 | }) |
| 512 | |
| 513 | it("reports an invalid reference", () => { |
| 514 | assert.strictEqual( |
| 515 | errorFrom(() => |
| 516 | SchemaRepresentation.fromRepresentation({ |
| 517 | representation: { _tag: "Reference", $ref: "Missing" }, |
| 518 | references: {} |
| 519 | }, { revivers: [] }) |
| 520 | ).message, |
| 521 | `Invalid reference Missing\n at ["representation"]["$ref"]` |
| 522 | ) |
| 523 | }) |
| 524 | }) |