allow specifying common base type for unions

This commit is contained in:
Florian Dold 2019-12-14 19:09:01 +01:00
parent 47b2b84135
commit a0b5aba71c
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B

View File

@ -118,10 +118,10 @@ class ObjectCodecBuilder<T, TC> {
} }
} }
class UnionCodecBuilder<T, D extends keyof T, TC> { class UnionCodecBuilder<T, D extends keyof T, B, TC> {
private alternatives = new Map<any, Alternative>(); private alternatives = new Map<any, Alternative>();
constructor(private discriminator: D) {} constructor(private discriminator: D, private baseCodec?: Codec<B>) {}
/** /**
* Define a property for the object. * Define a property for the object.
@ -129,7 +129,7 @@ class UnionCodecBuilder<T, D extends keyof T, TC> {
alternative<V>( alternative<V>(
tagValue: T[D], tagValue: T[D],
codec: Codec<V>, codec: Codec<V>,
): UnionCodecBuilder<T, D, TC | V> { ): UnionCodecBuilder<T, D, B, TC | V> {
this.alternatives.set(tagValue, { codec, tagValue }); this.alternatives.set(tagValue, { codec, tagValue });
return this as any; return this as any;
} }
@ -140,21 +140,36 @@ class UnionCodecBuilder<T, D extends keyof T, TC> {
* @param objectDisplayName name of the object that this codec operates on, * @param objectDisplayName name of the object that this codec operates on,
* used in error messages. * used in error messages.
*/ */
build<R extends TC>(objectDisplayName: string): Codec<R> { build<R extends TC & B>(objectDisplayName: string): Codec<R> {
const alternatives = this.alternatives; const alternatives = this.alternatives;
const discriminator = this.discriminator; const discriminator = this.discriminator;
const baseCodec = this.baseCodec;
return { return {
decode(x: any, c?: Context): R { decode(x: any, c?: Context): R {
const d = x[discriminator]; const d = x[discriminator];
if (d === undefined) { if (d === undefined) {
throw new DecodingError(`expected tag for ${objectDisplayName} at ${renderContext(c)}.${discriminator}`); throw new DecodingError(
`expected tag for ${objectDisplayName} at ${renderContext(
c,
)}.${discriminator}`,
);
} }
const alt = alternatives.get(d); const alt = alternatives.get(d);
if (!alt) { if (!alt) {
throw new DecodingError(`unknown tag for ${objectDisplayName} ${d} at ${renderContext(c)}.${discriminator}`); throw new DecodingError(
`unknown tag for ${objectDisplayName} ${d} at ${renderContext(
c,
)}.${discriminator}`,
);
} }
return alt.codec.decode(x); const altDecoded = alt.codec.decode(x);
} if (baseCodec) {
const baseDecoded = baseCodec.decode(x, c);
return { ...baseDecoded, ...altDecoded };
} else {
return altDecoded;
}
},
}; };
} }
} }
@ -180,10 +195,12 @@ export function stringConstCodec<V extends string>(s: V): Codec<V> {
if (x === s) { if (x === s) {
return x; return x;
} }
throw new DecodingError(`expected string constant "${s}" at ${renderContext(c)}`); throw new DecodingError(
} `expected string constant "${s}" at ${renderContext(c)}`,
} );
}; },
};
}
/** /**
* Return a codec for a value that must be a number. * Return a codec for a value that must be a number.
@ -234,8 +251,11 @@ export function mapCodec<T>(innerCodec: Codec<T>): Codec<{ [x: string]: T }> {
} }
export class UnionCodecPreBuilder<T> { export class UnionCodecPreBuilder<T> {
discriminateOn<D extends keyof T>(discriminator: D): UnionCodecBuilder<T, D, never> { discriminateOn<D extends keyof T, B>(
return new UnionCodecBuilder<T, D, never>(discriminator); discriminator: D,
baseCodec?: Codec<B>,
): UnionCodecBuilder<T, D, B, never> {
return new UnionCodecBuilder<T, D, B, never>(discriminator, baseCodec);
} }
} }