diff options
Diffstat (limited to 'packages/taler-util/src/codec.ts')
-rw-r--r-- | packages/taler-util/src/codec.ts | 105 |
1 files changed, 102 insertions, 3 deletions
diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts index 8605ff335..678c3f092 100644 --- a/packages/taler-util/src/codec.ts +++ b/packages/taler-util/src/codec.ts @@ -14,12 +14,17 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { j2s } from "./helpers.js"; +import { Logger } from "./logging.js"; + /** * Type-safe codecs for converting from/to JSON. */ /* eslint-disable @typescript-eslint/ban-types */ +const logger = new Logger("codec.ts"); + /** * Error thrown when decoding fails. */ @@ -134,7 +139,7 @@ class UnionCodecBuilder< TargetType, TagPropertyLabel extends keyof TargetType, CommonBaseType, - PartialTargetType + PartialTargetType, > { private alternatives = new Map<any, Alternative>(); @@ -186,7 +191,7 @@ class UnionCodecBuilder< throw new DecodingError( `expected tag for ${objectDisplayName} at ${renderContext( c, - )}.${discriminator}`, + )}.${String(discriminator)}`, ); } const alt = alternatives.get(d); @@ -194,7 +199,7 @@ class UnionCodecBuilder< throw new DecodingError( `unknown tag for ${objectDisplayName} ${d} at ${renderContext( c, - )}.${discriminator}`, + )}.${String(discriminator)}`, ); } const altDecoded = alt.codec.decode(x); @@ -322,6 +327,74 @@ export function codecForString(): Codec<string> { } /** + * Return a codec for a value that must be a string. + */ +export function codecForStringURL(shouldEndWithSlash?: boolean): Codec<string> { + return { + decode(x: any, c?: Context): string { + if (typeof x !== "string") { + throw new DecodingError( + `expected string at ${renderContext(c)} but got ${typeof x}`, + ); + } + if (shouldEndWithSlash && !x.endsWith("/")) { + throw new DecodingError( + `expected URL string that ends with slash at ${renderContext( + c, + )} but got ${x}`, + ); + } + try { + const url = new URL(x); + return x; + } catch (e) { + if (e instanceof Error) { + throw new DecodingError(e.message); + } else { + throw new DecodingError( + `expected an URL string at ${renderContext(c)} but got "${x}"`, + ); + } + } + }, + }; +} + +/** + * Return a codec for a value that must be a string. + */ +export function codecForURL(shouldEndWithSlash?: boolean): Codec<URL> { + return { + decode(x: any, c?: Context): URL { + if (typeof x !== "string") { + throw new DecodingError( + `expected string at ${renderContext(c)} but got ${typeof x}`, + ); + } + if (shouldEndWithSlash && !x.endsWith("/")) { + throw new DecodingError( + `expected URL string that ends with slash at ${renderContext( + c, + )} but got ${x}`, + ); + } + try { + const url = new URL(x); + return url; + } catch (e) { + if (e instanceof Error) { + throw new DecodingError(e.message); + } else { + throw new DecodingError( + `expected an URL string at ${renderContext(c)} but got "${x}"`, + ); + } + } + }, + }; +} + +/** * Codec that allows any value. */ export function codecForAny(): Codec<any> { @@ -417,3 +490,29 @@ export function codecOptional<V>(innerCodec: Codec<V>): Codec<V | undefined> { }, }; } + +export type CodecType<T> = T extends Codec<infer X> ? X : any; + +export function codecForEither<T extends Array<Codec<unknown>>>( + ...alts: [...T] +): Codec<CodecType<T[number]>> { + return { + decode(x: any, c?: Context): any { + for (const alt of alts) { + try { + return alt.decode(x, c); + } catch (e) { + continue; + } + } + if (logger.shouldLogTrace()) { + logger.trace(`offending value: ${j2s(x)}`); + } + throw new DecodingError( + `No alternative matched at at ${renderContext(c)}`, + ); + }, + }; +} + +const x = codecForEither(codecForString(), codecForNumber()); |