Skip to content

Commit

Permalink
feat: split blueprint and field so schema is attached
Browse files Browse the repository at this point in the history
  • Loading branch information
tyler-johnson committed Mar 24, 2017
1 parent 3788e26 commit 1981932
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 144 deletions.
79 changes: 31 additions & 48 deletions src/blueprint.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,23 @@
import { Record, Map, List } from "immutable";
import Schema, {defaultSchema} from "./schema";
import { Record } from "immutable";
import { toPath } from "lodash";
import Schema from "./schema";
import Field from "./field";

const DEFAULTS = {
key: null,
type: null,
options: null,
props: null
root: null,
schema: null
};

export default class Blueprint extends Record(DEFAULTS) {
static create(blueprint = {}) {
if (Blueprint.isBlueprint(blueprint)) return blueprint;

if (typeof blueprint !== "object" || blueprint == null) {
throw new Error("Expecting object for blueprint.");
static create(props = {}) {
if (Blueprint.isBlueprint(props)) {
return props;
}

let {
key=null,
type=null,
options,
...props
} = blueprint;

options = Blueprint.createList(options);
props = Map.isMap(props) ? props : Map(props);

return new Blueprint({ key, type, options, props });
}

static createList(blueprints) {
if (List.isList(blueprints)) return blueprints;

if (Array.isArray(blueprints)) {
blueprints = blueprints.map(Blueprint.create);
} else if (typeof blueprints === "object" && blueprints != null) {
blueprints = Object.keys(blueprints).map(key => {
return Blueprint.create({ ...blueprints[key], key });
});
}
props.root = Field.create(props.root);
props.schema = Schema.create(props.schema);

return List(blueprints);
return new Blueprint(props).normalize();
}

static isBlueprint(b) {
Expand All @@ -51,24 +28,30 @@ export default class Blueprint extends Record(DEFAULTS) {
return "blueprint";
}

getOption(key) {
return this.options.find(o => o.key === key);
}
getField(key) {
const path = toPath(key);
let field = this.root;

while (path.length && field) {
field = field.getChildField(path.shift());
}

transform(value, schema=defaultSchema) {
return schema.transform(value, this);
return field;
}

normalize(schema=defaultSchema) {
return schema.normalize(this);
transform(value) {
return this.schema.transform(value, this.root);
}

join(schema, ...args) {
if (!Schema.isSchema(schema)) {
args.unshift(schema);
schema = defaultSchema;
}
normalize() {
return this.merge({
root: this.schema.normalize(this.root)
});
}

return schema.join(this, ...args);
join(...blueprints) {
const fields = [this].concat(blueprints).map(b => b.root);
const root = this.schema.join(...fields);
return this.merge({ root });
}
}
58 changes: 58 additions & 0 deletions src/field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Record, Map, List } from "immutable";

const DEFAULTS = {
key: null,
type: null,
children: null,
props: null
};

export default class Field extends Record(DEFAULTS) {
static create(field = {}) {
if (Field.isField(field)) {
return field;
}

if (typeof field !== "object" || field == null) {
throw new Error("Expecting object for field.");
}

let {
key=null,
type=null,
children,
...props
} = field;

children = Field.createList(children);
props = Map.isMap(props) ? props : Map(props);

return new Field({ key, type, props, children });
}

static createList(fields) {
if (List.isList(fields)) return fields;

if (Array.isArray(fields)) {
fields = fields.map(Field.create);
} else if (typeof fields === "object" && fields != null) {
fields = Object.keys(fields).map(key => {
return Field.create({ ...fields[key], key });
});
}

return List(fields);
}

static isField(b) {
return b instanceof Field;
}

static kind() {
return "field";
}

getChildField(key) {
return this.children.find(o => o.key === key);
}
}
8 changes: 4 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Blueprint from "./blueprint";
import Schema, {defaultSchema} from "./schema";
import Field from "./field";
import * as rules from "./rules/index";

export {
Blueprint,
Schema,
Field,
defaultSchema,
rules
};

export function normalize(blueprint, schema=defaultSchema) {
blueprint = Blueprint.create(blueprint);
schema = Schema.create(schema);
return schema.normalize(blueprint);
export default function(field, schema=defaultSchema) {
return Blueprint.create({ root: field, schema });
}
8 changes: 4 additions & 4 deletions src/rules/defaults.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// rule for defaults

function match(bp) {
return bp.props.has("default");
function match(field) {
return field.props.has("default");
}

function transform(value, bp) {
const def = bp.props.get("default");
function transform(value, field) {
const def = field.props.get("default");

return (typeof def !== "undefined") &&
(typeof value === "undefined" || value === "") ?
Expand Down
9 changes: 3 additions & 6 deletions src/rules/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import legacyRoot from "./legacy-root";
import legacySection from "./legacy-section";
import legacyRoot from "./legacy-root";
import section from "./section";
import sectionList from "./section-list";
import list from "./list";
import defaults from "./defaults";

export {
legacyRoot,
legacySection,
legacyRoot,
section,
sectionList,
list,
defaults
};

export default [
legacyRoot,
legacySection,
legacyRoot,
section,
sectionList,
list,
defaults
];
17 changes: 9 additions & 8 deletions src/rules/legacy-root.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// rule for v1 root objects

import Blueprint from "../blueprint";
import Field from "../field";

function match(bp) {
return !bp.type &&
!bp.options.size &&
bp.props.size;
function match(field) {
return !field.type &&
!field.children.size &&
field.props.size;
}

function normalize(bp) {
return Blueprint.create({
function normalize(field) {
return Field.create({
key: field.key,
type: "section",
options: bp.props.toJSON()
children: field.props.toJSON()
});
}

Expand Down
17 changes: 11 additions & 6 deletions src/rules/legacy-section.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// rule for v1 type-less sections

function match(bp) {
return !bp.type && bp.options;
import Field from "../field";

function match(field) {
return !field.type && field.props.has("options");
}

function normalize(bp) {
if (bp.type !== "section") {
return bp.merge({ type: "section" });
}
function normalize(field) {
const opts = field.props.get("options");

return field.merge({
type: "section",
children: Field.createList(opts)
});
}

export default {
Expand Down
24 changes: 12 additions & 12 deletions src/rules/list.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// rule for list composition

import Blueprint from "../blueprint";
import Field from "../field";

function match(blueprint) {
return blueprint.type === "list";
function match(field) {
return field.type === "list";
}

function normalize(blueprint) {
const option = blueprint.props.get("option");
if (!option) return blueprint;
function normalize(field) {
const child = field.props.get("field");
if (!child) return field;

return blueprint.merge({
options: Blueprint.createList([ option ])
return field.merge({
children: Field.createList([ child ])
});
}

function transform(value, blueprint) {
function transform(value, field) {
if (!Array.isArray(value)) return value;

const option = blueprint.options.first();
if (!option) return value;
const child = field.children.first();
if (!child) return value;

return value.map(v => {
return this.transform(v, option);
return this.transform(v, child);
});
}

Expand Down
20 changes: 0 additions & 20 deletions src/rules/section-list.js

This file was deleted.

26 changes: 13 additions & 13 deletions src/rules/section.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,42 @@

import { List, Set } from "immutable";

function match(bp) {
return bp.type === "section";
function match(field) {
return field.type === "section";
}

function transform(value, blueprint) {
function transform(value, field) {
if (typeof value === "undefined") value = {};
if (value == null || value.constructor !== Object) return value;

const keys = Object.keys(value);
blueprint.options.forEach(o => {
field.children.forEach(o => {
if (o.key && !keys.includes(o.key)) keys.push(o.key);
});

return keys.reduce((m, k) => {
const bp = blueprint.getOption(k);
if (!bp) m[k] = value[k];
else m[k] = this.transform(value[k], bp);
const child = field.getChildField(k);
if (!child) m[k] = value[k];
else m[k] = this.transform(value[k], child);
return m;
}, {});
}

function join(a, b) {
if (b.type !== "section") return a;

const keys = Set(a.options.map(o => o.key))
.union(b.options.map(o => o.key));
const keys = Set(a.children.map(o => o.key))
.union(b.children.map(o => o.key));

const options = keys.reduce((m, key) => {
const aopt = a.getOption(key);
const bopt = b.getOption(key);
const children = keys.reduce((m, key) => {
const aopt = a.getChildField(key);
const bopt = b.getChildField(key);
if (!aopt && !bopt) return m;
const opt = !aopt ? bopt : !bopt ? aopt : this.join(aopt, bopt);
return m.push(opt);
}, List());

return a.merge({ options });
return a.merge({ children });
}

export default {
Expand Down
Loading

0 comments on commit 1981932

Please sign in to comment.