From b93a9ebca5efec819351b7ace52c7f8bcf5ee6d1 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 29 Oct 2023 20:29:38 +0100 Subject: [PATCH] Generalize the "fieldsUnion with common fields" example --- .../fieldsUnion-with-common-fields.test.ts | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/examples/fieldsUnion-with-common-fields.test.ts b/examples/fieldsUnion-with-common-fields.test.ts index c744cb0..c41d26d 100644 --- a/examples/fieldsUnion-with-common-fields.test.ts +++ b/examples/fieldsUnion-with-common-fields.test.ts @@ -4,7 +4,6 @@ import { expect, test } from "vitest"; import { boolean, Codec, - DecoderResult, fieldsAuto, fieldsUnion, Infer, @@ -16,6 +15,46 @@ import { import { run } from "../tests/helpers"; test("fieldsUnion with common fields", () => { + // This function takes two codecs for object types and returns + // a new codec which is the intersection of those. + // This function is not part of the tiny-decoders package because it has some caveats: + // - If either codec uses `{ allowExtraFields: false }`, it fails. + // - It’s not possible to disallow extra fields on the resulting codec. + // - It’s type wise possible to intersect with a `Record`, but what does that mean? + function intersection< + Decoded1 extends Record, + Encoded1 extends Record, + Decoded2 extends Record, + Encoded2 extends Record, + >( + codec1: Codec, + codec2: Codec, + ): Codec { + return { + decoder: (value) => { + const decoderResult1 = codec1.decoder(value); + if (decoderResult1.tag === "DecoderError") { + return decoderResult1; + } + const decoderResult2 = codec2.decoder(value); + if (decoderResult2.tag === "DecoderError") { + return decoderResult2; + } + return { + tag: "Valid", + value: { + ...decoderResult1.value, + ...decoderResult2.value, + }, + }; + }, + encoder: (event) => ({ + ...codec1.encoder(event), + ...codec2.encoder(event), + }), + }; + } + type EventWithPayload = Infer; const EventWithPayload = fieldsUnion("event", [ { @@ -38,34 +77,8 @@ test("fieldsUnion with common fields", () => { timestamp: string, }); - type EncodedEvent = InferEncoded & - InferEncoded; - - type Event = EventMetadata & EventWithPayload; - - const Event: Codec = { - decoder: (value: unknown): DecoderResult => { - const eventMetadataResult = EventMetadata.decoder(value); - if (eventMetadataResult.tag === "DecoderError") { - return eventMetadataResult; - } - const eventWithPayloadResult = EventWithPayload.decoder(value); - if (eventWithPayloadResult.tag === "DecoderError") { - return eventWithPayloadResult; - } - return { - tag: "Valid", - value: { - ...eventMetadataResult.value, - ...eventWithPayloadResult.value, - }, - }; - }, - encoder: (event: Event): EncodedEvent => ({ - ...EventMetadata.encoder(event), - ...EventWithPayload.encoder(event), - }), - }; + type Event = Infer; + const Event = intersection(EventMetadata, EventWithPayload); expectType< TypeEqual< @@ -92,7 +105,7 @@ test("fieldsUnion with common fields", () => { expectType< TypeEqual< - EncodedEvent, + InferEncoded, { id: string; timestamp: string;