diff --git a/.changeset/wild-olives-exercise.md b/.changeset/wild-olives-exercise.md new file mode 100644 index 00000000..1c04ab8a --- /dev/null +++ b/.changeset/wild-olives-exercise.md @@ -0,0 +1,5 @@ +--- +"@tsplus/runtime": patch +--- + +Add Encoder, Guard and Decoder diff --git a/packages/runtime/_src/Decoder.ts b/packages/runtime/_src/Decoder.ts index 7a421151..3a38b835 100644 --- a/packages/runtime/_src/Decoder.ts +++ b/packages/runtime/_src/Decoder.ts @@ -132,6 +132,17 @@ export class DecoderErrorLiteral implements Decoder.Error { } } +export class DecoderErrorNull implements Decoder.Error { + constructor( + readonly value: unknown + ) {} + render = () => { + return Tree( + `Expected null instead received one of type "${typeof this.value}"` + ) + } +} + export class DecoderErrorStructMissingField implements Decoder.Error { render = () => Tree(`Missing`) } @@ -250,6 +261,15 @@ export const number: Decoder = Decoder((u) => Result.fail(new DecoderErrorPrimitive(u, "number")) ) +/** + * @tsplus implicit + */ +export const _null: Decoder = Decoder((u) => + Derive>().is(u) ? + Result.success(u) : + Result.fail(new DecoderErrorNull(u)) +) + /** * @tsplus implicit */ diff --git a/packages/runtime/_src/Encoder.ts b/packages/runtime/_src/Encoder.ts index f0cc5534..f9ddc78d 100644 --- a/packages/runtime/_src/Encoder.ts +++ b/packages/runtime/_src/Encoder.ts @@ -52,6 +52,13 @@ export const boolean: Encoder = Encoder((u) => u) */ export const number: Encoder = Encoder((u) => u) +/** + * Encoder for null + * + * @tsplus implicit + */ +export const _null: Encoder = Encoder((u) => u) + /** * Encoder for a string * diff --git a/packages/runtime/_src/Guard.ts b/packages/runtime/_src/Guard.ts index b5865042..da911d92 100644 --- a/packages/runtime/_src/Guard.ts +++ b/packages/runtime/_src/Guard.ts @@ -56,6 +56,13 @@ export const boolean: Guard = Guard((u): u is boolean => typeof u === " */ export const number: Guard = Guard((u): u is number => typeof u === "number") +/** + * Guard for null + * + * @tsplus implicit + */ +export const _null: Guard = Guard((u): u is null => u === null) + /** * Guard for a string * diff --git a/packages/runtime/_test/Decoder.test.ts b/packages/runtime/_test/Decoder.test.ts index 9bfe6baa..136c840d 100644 --- a/packages/runtime/_test/Decoder.test.ts +++ b/packages/runtime/_test/Decoder.test.ts @@ -92,6 +92,18 @@ describe.concurrent("Decoder", () => { `Expected literal "1" of type "number" instead received "200"` ) }) + it("null", () => { + const decoder: Decoder = Derive() + assert.deepEqual(decoder.decodeJSON(JSON.stringify(null)).right.value, null) + assert.deepEqual( + decoder.decodeJSON(JSON.stringify("no-hello")).left.value?.message, + `Expected null instead received one of type "string"` + ) + assert.deepEqual( + decoder.decodeJSON(JSON.stringify(200)).left.value?.message, + `Expected null instead received one of type "number"` + ) + }) it("struct", () => { interface Person { firstName: string diff --git a/packages/runtime/_test/Encoder.test.ts b/packages/runtime/_test/Encoder.test.ts index 6130ae43..4655a4df 100644 --- a/packages/runtime/_test/Encoder.test.ts +++ b/packages/runtime/_test/Encoder.test.ts @@ -12,6 +12,10 @@ describe.concurrent("Encoder", () => { const number: Encoder = Derive() assert.deepEqual(number.encode(1), 1) }) + it("null", () => { + const { encode }: Encoder = Derive() + assert.deepEqual(encode(null), null) + }) it("date", () => { const string: Encoder = Derive() const date = new Date()