summaryrefslogtreecommitdiff
path: root/packages/taler-util/src/codec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-util/src/codec.ts')
-rw-r--r--packages/taler-util/src/codec.ts105
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());