import {Option} from 'kung-fu'; export class DecodeError extends Error { constructor(message: string) { super(); (Error as any).captureStackTrace(this, 'DecodeError'); this.name = 'DecodeError'; this.message = message; } } export class Decoder { constructor(private _run: (data: any) => T) {} decodeObject(data: Object): T { return this._run(data); } decodeString(data: string): T { return this._run(JSON.parse(data)); } map(f: (t: T) => U): Decoder { return new Decoder(data => f(this._run(data))); } andThen(f: (t: T) => Decoder): Decoder { return new Decoder(data => f(this._run(data))._run(data)); } reason(f: (reason: string) => string): Decoder { return new Decoder(data => { try { return this._run(data); } catch (err) { if (err instanceof DecodeError) { throw new DecodeError(f(err.message)); } throw err; } }); } static fail(message: string): Decoder { return new Decoder((data): T => { throw new DecodeError(message); }); } static succeed(value: T): Decoder { return new Decoder(data => value); } static get string(): Decoder { return new Decoder(data => { if (data === null) { throw new DecodeError('data is null'); } if (typeof data !== 'string') { throw new DecodeError('data is not a string'); } return data; }); } static get number(): Decoder { return new Decoder(data => { if (data === null) { throw new DecodeError('data is null'); } if (typeof data !== 'number') { throw new DecodeError('data is not a number'); } return data; }); } static get integer(): Decoder { return Decoder.number.map(num => { if (!Number.isInteger(num)) { throw new DecodeError('data is not an integer'); } return num; }); } static get boolean(): Decoder { return new Decoder(data => { if (data === null) { throw new DecodeError('data is null'); } if (typeof data !== 'boolean') { throw new DecodeError('data is not a boolean'); } return data; }); } maybe(): Decoder> { return new Decoder(data => { if (data === null || typeof data === 'undefined') { return Option.none(); } return Option.some(this._run(data)); }); } array(): Decoder> { return new Decoder(data => { if (!Array.isArray(data)) { throw new DecodeError('data is not an array'); } return data.map((inner: any) => this._run(inner)); }); } at(path: Array): Decoder { return new Decoder(data => { const inner = path.reduce((intermediate: any, pathComponent: string) => { return intermediate && intermediate[pathComponent]; }, data); return this._run(inner); }); } dict(): Decoder<{[key: string]: T}> { return new Decoder(data => { const map: {[key: string]: T} = {}; Object.keys(data).forEach(key => { map[key] = this._run(data[key]); }); return map; }); } static oneOf(decoders: Array>): Decoder { return new Decoder(data => { let lastErr: Error; for (let decoder of decoders) { try { return decoder._run(data); } catch (err) { lastErr = err; } } throw lastErr; }); } static object2(a: Decoder, b: Decoder, f: (a: A, b: B) => X): Decoder { return new Decoder(data => { return f(a._run(data), b._run(data)); }); } static object3(a: Decoder, b: Decoder, c: Decoder, f: (a: A, b: B, c: C) => X): Decoder { return new Decoder(data => { return f(a._run(data), b._run(data), c._run(data)); }); } static object4(a: Decoder, b: Decoder, c: Decoder, d: Decoder, f: (a: A, b: B, c: C, d: D) => X): Decoder { return new Decoder(data => { return f(a._run(data), b._run(data), c._run(data), d._run(data)); }); } static object5(a: Decoder, b: Decoder, c: Decoder, d: Decoder, e: Decoder, f: (a: A, b: B, c: C, d: D, e: E) => X): Decoder { return new Decoder(data => { return f(a._run(data), b._run(data), c._run(data), d._run(data), e._run(data)); }); } static object6(a: Decoder, b: Decoder, c: Decoder, d: Decoder, e: Decoder, f: Decoder, x: (a: A, b: B, c: C, d: D, e: E, f: F) => X): Decoder { return new Decoder(data => { return x(a._run(data), b._run(data), c._run(data), d._run(data), e._run(data), f._run(data)); }); } static object7(a: Decoder, b: Decoder, c: Decoder, d: Decoder, e: Decoder, f: Decoder, g: Decoder, x: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => X): Decoder { return new Decoder(data => { return x(a._run(data), b._run(data), c._run(data), d._run(data), e._run(data), f._run(data), g._run(data)); }); } static object8(a: Decoder, b: Decoder, c: Decoder, d: Decoder, e: Decoder, f: Decoder, g: Decoder, h: Decoder, x: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => X): Decoder { return new Decoder(data => { return x(a._run(data), b._run(data), c._run(data), d._run(data), e._run(data), f._run(data), g._run(data), h._run(data)); }); } }