Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export * from "./lib/result";
export {
// Because lib/result has a bunch of pretty-printing helpers and such,
// we only export specific thigns publically
Result,
Err,
StructuralError,
} from "./lib/result";
export * from "./lib/type";
export * from "./lib/get-type";
export * from "./lib/match"
Expand Down
4 changes: 4 additions & 0 deletions lib/checks/any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export class Any extends Type<any> {
check(val: any): Result<any> {
return val;
}

toString() {
return 'any'
}
}

export const any = new Any();
20 changes: 15 additions & 5 deletions lib/checks/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ export class Arr<T> extends Type<Array<T>> {
}

check(val: any): Result<Array<T>> {
if(!Array.isArray(val)) return new Err(`${val} is not an array`);
if(!Array.isArray(val)) return this.err(`not an array`, val)

for(const el of val) {
const result = this.elementType.check(el);
// Don't bother collecting all errors in an array: for long arrays this is very obnoxious
if(result instanceof Err) return new Err(result.message);

// use traditional iteration so we know the index
for (let i = 0; i<val.length; i++) {
if (i in val) { // support sparse arrays
const el = val[i]
const result = this.elementType.check(el);
if (result instanceof Err) {
return Err.lift(result, i)
}
}
}

// If we got this far, there were no errors; it's an Array<T>
return val as Array<T>;
}

toString() {
return `Array<${this.elementType}>`
}
}

export function array<T>(t: Type<T>): Arr<T> {
Expand Down
12 changes: 8 additions & 4 deletions lib/checks/dict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ export class Dict<V> extends Type<RawDict<V>> {
}

check(val: any): Result<RawDict<V>> {
if(typeof val !== 'object') return new Err(`${val} is not an object`);
if(Array.isArray(val)) return new Err(`${val} is an array`);
if(val === null) return new Err(`${val} is null`);
if(typeof val !== 'object') return this.err(`not an object`, val);
if(Array.isArray(val)) return this.err(`is an array`, val);
if(val === null) return this.err(`is null`, val);

for(const prop in val) {
const result = this.valueType.check(val[prop]);
if(result instanceof Err) return new Err(`[${prop}]: ${result.message}`);
if(result instanceof Err) return Err.lift(result, prop)
}

return val as Result<RawDict<V>>;
}

toString() {
return `{ [key: string]: ${this.valueType} }`
}
}

export function dict<V>(v: Type<V>): Dict<V> {
Expand Down
11 changes: 9 additions & 2 deletions lib/checks/instance-of.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Result } from "../result";
import { Type } from "../type";

type Constructor<T> = Function & { prototype: T }
Expand All @@ -13,7 +13,14 @@ export class InstanceOf<T> extends Type<T> {

check(val: any): Result<T> {
if(val instanceof this.klass) return val;
return new Err(`${val} is not an instance of ${this.klass}`);
return this.err(() => `not an instance of ${this.klass}`, val);
}

toString() {
if (this.klass.name) {
return this.klass.name
}
return `instanceof ${this.klass}`
}
}

Expand Down
8 changes: 6 additions & 2 deletions lib/checks/is.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Result } from "../result";
import { Type } from "../type";

type Guard<T> = (val: any) => val is T
Expand All @@ -15,7 +15,11 @@ export class Is<T> extends Type<T> {

check(val: any): Result<T> {
if(this.isT(val)) return val;
return new Err(`${val} is not a ${this.name} (guard failed)`)
return this.err(`guard failed`, val)
}

toString() {
return `is(${this.name}, ${this.isT})`
}
}

Expand Down
14 changes: 10 additions & 4 deletions lib/checks/map.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Err, MapKey, MapKeyIndex, Result } from "../result";
import { Type } from "../type";

export class MapType<K, V> extends Type<Map<K, V>> {
Expand All @@ -12,17 +12,23 @@ export class MapType<K, V> extends Type<Map<K, V>> {
}

check(val: any): Result<Map<K, V>> {
if(!(val instanceof Map)) return new Err(`${val} is not an instance of Map`);
if(!(val instanceof Map)) return this.err(`not a Map`, val);

let i = 0
for(const [k, v] of val) {
const kResult = this.keyType.check(k);
if(kResult instanceof Err) return new Err(`{val} key error: ${kResult.message}`);
if(kResult instanceof Err) return Err.lift(kResult, new MapKeyIndex(i))
const vResult = this.valueType.check(v);
if(vResult instanceof Err) return new Err(`{val} value error: ${vResult.message}`);
if(vResult instanceof Err) return Err.lift(vResult, new MapKey(k))
i++
}

return val as Map<K, V>;
}

toString() {
return `Map<${this.keyType}, ${this.valueType}>`
}
}

export function map<K, V>(k: Type<K>, v: Type<V>): MapType<K, V> {
Expand Down
12 changes: 2 additions & 10 deletions lib/checks/never.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,2 @@
import { Result, Err } from "../result";
import { Type } from "../type";

export class Never extends Type<never> {
check(_: any): Result<never> {
return new Err('never')
}
}

export const never = new Never();
import { never, Never } from "../type";
export { never, Never }
12 changes: 9 additions & 3 deletions lib/checks/set.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Err, Result, MapKeyIndex } from "../result";
import { Type } from "../type";

export class SetType<V> extends Type<Set<V>> {
Expand All @@ -10,13 +10,19 @@ export class SetType<V> extends Type<Set<V>> {
}

check(val: any): Result<Set<V>> {
if(!(val instanceof Set)) return new Err(`${val} is not an instance of Set`);
if(!(val instanceof Set)) return this.err(`not an instance of Set`, val);
let i = 0
for(const v of val) {
const result = this.valueType.check(v);
if(result instanceof Err) return new Err(`{val} failed set check on value ${v}: ${result.message}`);
if(result instanceof Err) return Err.lift(result, new MapKeyIndex(i))
i++
}
return val as Set<V>;
}

toString() {
return `Set<${this.valueType}>`
}
}

export function set<V>(v: Type<V>): SetType<V> {
Expand Down
42 changes: 33 additions & 9 deletions lib/checks/struct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err } from "../result";
import { Err, shouldWrap, indentNext } from "../result";
import { KeyTrackResult, Type, KeyTrackingType } from "../type";
import { GetType } from "../get-type";

Expand Down Expand Up @@ -72,35 +72,59 @@ export class Struct<T extends TypeStruct> extends KeyTrackingType<UnwrappedTypeS
}
}

return new Err(`${val} failed the following checks:\n${errs.join('\n')}`);
if (errs.length === 1) {
return errs[0]
}

return Err.combine(errs, {
value: val,
type: this,
})
}

private checkType(val: any): Err<UnwrappedTypeStruct<T>> | undefined {
if(typeof val !== 'object') return new Err(`${val} is not an object`);
if(Array.isArray(val)) return new Err(`${val} is an array`);
if(val === null) return new Err(`${val} is null`);
if(typeof val !== 'object') return this.err(`isn't an object`, val)
if(Array.isArray(val)) return this.err('is an array', val)
if(val === null) return this.err('is null', val)
return undefined;
}

private checkFields(val: any): string[] {
const errs: string[] = [];
private checkFields(val: any): Err<any>[] {
const errs: Err<any>[] = [];
for(const prop in this.definition) {
const field = this.definition[prop]
if (!(prop in val)) {
if (isOptional(field)) {
continue;
}

errs.push(`missing key '${prop}'`);
errs.push(new Err('key missing', {
path: [prop],
type: keyType(field),
value: undefined,
}))
continue;
}

const result = keyType(field).check(val[prop]);
if(result instanceof Err) errs.push(result.message);
if(result instanceof Err) errs.push(Err.lift(result, prop));
}

return errs;
}

toString() {
const kvs: string[] = []
Object.keys(this.definition).forEach(key => {
const value = this.definition[key]
const keystr = isOptional(value) ? `${key}?: ` : `${key}: `
// is this cooL??? hard to reason about
const valstr = keyType(value).toString()
kvs.push(keystr + valstr)
})
const sep = shouldWrap(kvs) ? ",\n" : ', '
return '{ ' + indentNext(kvs.join(sep)) + ' }'
}
}

type HiddenStruct<T extends TypeStruct> = Type<UnwrappedTypeStruct<T>>;
Expand Down
8 changes: 6 additions & 2 deletions lib/checks/type-of.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Result } from "../result";
import { Type } from "../type";

export class TypeOf<T> extends Type<T> {
Expand All @@ -10,7 +10,11 @@ export class TypeOf<T> extends Type<T> {

check(val: any): Result<T> {
if(typeof val === this.typestring) return val as T;
return new Err(`${val} is not a ${this.typestring}`);
return this.err(`not a ${this.typestring}`, val)
}

toString() {
return this.typestring
}
}

Expand Down
14 changes: 12 additions & 2 deletions lib/checks/value.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Result } from "../result";
import { Result } from "../result";
import { Type } from "../type";

export class Value<T> extends Type<T> {
Expand All @@ -10,7 +10,17 @@ export class Value<T> extends Type<T> {

check(val: any): Result<T> {
if(val === this.val) return val;
return new Err(`${val} is not equal to ${this.val}`);
return this.err('not equal', val)
}

toString() {
if (typeof this.val === 'string' ||
typeof this.val === 'number' ||
this.val === null
) {
return JSON.stringify(this.val)
}
return `=== ${this.val}`
}
}

Expand Down
Loading