From 60d154c36bbd6773bbed44da82b17f211604c4b4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sat, 14 Dec 2019 17:55:31 +0100 Subject: union codecs, error messages --- src/util/codec.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) (limited to 'src/util/codec.ts') diff --git a/src/util/codec.ts b/src/util/codec.ts index 690486b7d..78516183c 100644 --- a/src/util/codec.ts +++ b/src/util/codec.ts @@ -69,6 +69,11 @@ interface Prop { codec: Codec; } +interface Alternative { + tagValue: any; + codec: Codec; +} + class ObjectCodecBuilder { private propList: Prop[] = []; @@ -89,10 +94,10 @@ class ObjectCodecBuilder { * @param objectDisplayName name of the object that this codec operates on, * used in error messages. */ - build(objectDisplayName: string): Codec { + build(objectDisplayName: string): Codec { const propList = this.propList; return { - decode(x: any, c?: Context): TC { + decode(x: any, c?: Context): R { if (!c) { c = { path: [`(${objectDisplayName})`], @@ -107,12 +112,53 @@ class ObjectCodecBuilder { ); obj[prop.name] = propVal; } - return obj as TC; + return obj as R; }, }; } } +class UnionCodecBuilder { + private alternatives = new Map(); + + constructor(private discriminator: D) {} + + /** + * Define a property for the object. + */ + alternative( + tagValue: T[D], + codec: Codec, + ): UnionCodecBuilder { + this.alternatives.set(tagValue, { codec, tagValue }); + return this as any; + } + + /** + * Return the built codec. + * + * @param objectDisplayName name of the object that this codec operates on, + * used in error messages. + */ + build(objectDisplayName: string): Codec { + const alternatives = this.alternatives; + const discriminator = this.discriminator; + return { + decode(x: any, c?: Context): R { + const d = x[discriminator]; + if (d === undefined) { + throw new DecodingError(`expected tag for ${objectDisplayName} at ${renderContext(c)}.${discriminator}`); + } + const alt = alternatives.get(d); + if (!alt) { + throw new DecodingError(`unknown tag for ${objectDisplayName} ${d} at ${renderContext(c)}.${discriminator}`); + } + return alt.codec.decode(x); + } + }; + } +} + /** * Return a codec for a value that must be a string. */ @@ -125,6 +171,20 @@ export const stringCodec: Codec = { }, }; +/** + * Return a codec for a value that must be a string. + */ +export function stringConstCodec(s: V): Codec { + return { + decode(x: any, c?: Context): V { + if (x === s) { + return x; + } + throw new DecodingError(`expected string constant "${s}" at ${renderContext(c)}`); + } + } +}; + /** * Return a codec for a value that must be a number. */ @@ -179,3 +239,9 @@ export function mapCodec(innerCodec: Codec): Codec<{ [x: string]: T }> { export function objectCodec(): ObjectCodecBuilder { return new ObjectCodecBuilder(); } + +export function unionCodec( + discriminator: D, +): UnionCodecBuilder { + return new UnionCodecBuilder(discriminator); +} -- cgit v1.2.3