Skip to content

Instantly share code, notes, and snippets.

@drd
Forked from bauerca/stuff-on-yr-gist.js
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drd/7e37b4126cb83bd9e7e4 to your computer and use it in GitHub Desktop.
Save drd/7e37b4126cb83bd9e7e4 to your computer and use it in GitHub Desktop.
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