-
-
Save drd/7e37b4126cb83bd9e7e4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let isObject = o => toString.call(o) === '[object Object]' | |
let consume = i => { | |
let iterator = i && i.keys; | |
if (!iterator) return; | |
let arr = []; | |
while (res = iterator.next(), !res.isDone) arr.push(res.value); | |
return res; | |
} | |
class Type { | |
constructor(value) { | |
this.raw = null; | |
this.valid = undefined; | |
value && this.set(value); | |
} | |
static clone(overrides) { | |
class cloned extends this {}; | |
Object.assign(cloned.prototype, overrides); | |
return cloned; | |
} | |
static named(name) { | |
return this.clone({name}); | |
} | |
static using(overrides) { | |
// maybe pre-process overrides? | |
return this.clone(overrides); | |
} | |
static fromDefaults() { | |
let defaulted = new this(); | |
defaulted.set(defaulted.default); | |
return defaulted; | |
} | |
} | |
Type.prototype.default = null; | |
Type.prototype.optional = false; | |
Type.prototype.validators = []; | |
class AdaptationError extends Error {}; | |
class Scalar extends Type { | |
constructor() { | |
super(); | |
this.value = this.serialized = null; | |
} | |
set(raw) { | |
this.raw = raw; | |
try { | |
this.value = this.adapt(raw); | |
} catch (e) { | |
try { | |
this.serialized = this.serialize(raw); | |
} catch (e) { | |
this.serialized = ''; | |
} | |
this.value = null; | |
return false; | |
} | |
this.serialized = this.serialize(this.value); | |
return true; | |
} | |
validate() { | |
if (this.value === null) { | |
this.valid = this.optional; | |
return this.valid; | |
} | |
this.valid = true; | |
this.validators.reduce((valid, v) => { | |
if (valid) { | |
valid = v.call(this); | |
} | |
return valid; | |
}, this.valid); | |
return this.valid; | |
} | |
} | |
class Str extends Scalar { | |
adapt(raw) { | |
return raw.toString(); | |
} | |
serialize(value) { | |
return value; | |
} | |
} | |
class Int extends Scalar { | |
adapt(raw) { | |
let value = parseInt(raw, 10); | |
if (isNaN(value)) { | |
throw new AdaptationError(`${value} is not a number`); | |
} | |
return value; | |
} | |
serialize(value) { | |
return value.toString(); | |
} | |
} | |
class List extends Type { | |
get value() { | |
return this.members.map(m => m.value); | |
} | |
set(raw) { | |
this.raw = raw; | |
if (!raw.forEach) { | |
return false; | |
} | |
let success = true; | |
this.members = []; | |
let items = []; | |
raw.forEach(mbr => { | |
let member = new this.memberType(); | |
success = success & member.set(mbr); | |
items.push(member); | |
}) | |
if (success) { | |
this.members = items; | |
} | |
} | |
static of(type) { | |
return this.clone({memberType: type}); | |
} | |
} | |
List.prototype.members = []; | |
class Map extends Type { | |
get value() { | |
return Object.keys(this.members).reduce((v, m) => { v[m] = this.members[m].value; return v; }, {}); | |
} | |
set(raw) { | |
this.raw = raw; | |
if (!(raw.keys || isObject(raw))) { | |
return false; | |
} | |
let get = (o, k) => o.keys ? o = o.get(k) : o[k]; | |
let keys = consume(raw.keys) || Object.keys(raw); | |
let members = {} | |
let success = keys.reduce((success, k) => { | |
let member = new this.memberSchema[k](); | |
members[k] = member; | |
return success &= member.set(raw[k]); | |
}, true); | |
if (success) { | |
// should this.members only be defined here? | |
// or in constructor? | |
this.members = members; | |
} | |
return success; | |
} | |
static of(...members) { | |
let memberSchema = members.reduce((ms, m) => { | |
ms[m.prototype.name] = m; | |
return ms; | |
}, {}); | |
return this.clone({memberSchema}); | |
} | |
} | |
function expect(thing) { | |
return { | |
value: thing, | |
toBe(value) { | |
if (value !== this.value) { | |
console.log(`${this.value} was not ${value}`); | |
//throw new Error(`${this.value} was not ${value}`); | |
} else { | |
//console.log(`${this.value} is ${value}`); | |
} | |
}, | |
toEqual(value) { | |
if (! this.value.every((x, i) => value[i] === x)) { | |
console.log(`${this.value} did not equal ${value}`); | |
} | |
} | |
} | |
} | |
var MyString = Str.named('string').using({default: 'default', optional: false}); | |
var s = new MyString(); | |
expect(s.name).toBe('string'); | |
expect(s.default).toBe('default'); | |
expect(s.optional).toBe(false); | |
// lifecycle | |
expect(s.value).toBe(null); | |
expect(s.valid).toBe(undefined); | |
expect(s.serialized).toBe(null); | |
expect(s.raw).toBe(null); | |
s.validate(); | |
expect(s.valid).toBe(false); | |
s = MyString.fromDefaults(); | |
expect(s.value).toBe(s.default); | |
s.validate(); | |
expect(s.valid).toBe(true); | |
expect(s.set(123)).toBe(true); | |
expect(s.value).toBe('123'); | |
expect(s.raw).toBe(123); | |
expect(s.serialized).toBe('123'); | |
s.validate(); | |
expect(s.valid).toBe(true); | |
let n = new Int(); | |
n.set('yr mom'); | |
expect(n.value).toBe(null) | |
n.set('123'); | |
expect(n.value).toBe(123) | |
var Strings = List.of(Str); | |
let ss = new Strings(); | |
ss.set(['yr', 'mom']); | |
expect(ss.value).toEqual(['yr', 'mom']) | |
var DefaultedStrings = Strings.using({default: ['foo', 'bar']}); | |
let ds = new DefaultedStrings(); | |
expect(ds.value).toEqual([]); | |
let dds = DefaultedStrings.fromDefaults(); | |
expect(ds.value).toEqual(DefaultedStrings.defaults); | |
var ABDict = Map.of(Str.named('a'), Int.named('b')); | |
let abd = new ABDict(); | |
abd.set({a: 'foo', b: 3}) | |
expect(abd.value.a).toBe('foo'); | |
expect(abd.value.b).toBe(3); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment