morlay
7/30/2015 - 4:21 AM

SchemaRecord

import isTypeObject from './isTypeObject';
import isFunction from 'lodash.isFunction';

export default function updateData(schema = {}, data = {}) {
  if (isTypeObject(schema)) {
    return schema
      .get('properties')
      .map((schemaItem, key)=> {
        const factory = schemaItem.get('factory');
        let values = data && data[key];
        return isFunction(factory) ? factory(values) : (values || schemaItem.get('default', null));
      })
      .toObject();
  }
}
import immutable from 'immutable';

export default function isTypeObject(schema = {}) {
  return immutable.Map.isMap(schema.get('properties'));
}
import immutable from 'immutable';

export default function isTypeArray(schema = {}) {
  return immutable.Map.isMap(schema.get('items'));
}
import isFunction from 'lodash.isFunction';
import isArray from 'lodash.isArray';
import Immutable from 'immutable';

import isTypeObject from './isTypeObject';
import isTypeArray from './isTypeArray';

import SchemaRecord from './../models/SchemaRecord';

export default function completeSchema(schema) {

  schema = schema.asMutable();

  if (isTypeObject(schema)) {
    return schema.update('properties', (schemaProps)=> {
        return schemaProps.map(updateSchemaItem);
      }
    );
  }

  function updateSchemaItem(schemaItem) {

    if (isTypeObject(schemaItem)) {
      const factory = schemaItem.get('factory');
      return Immutable.fromJS({
        factory: isFunction(factory) ? factory : SchemaRecord.createFactory(SchemaRecord(schemaItem.toJS()))
      });
    }

    if (isTypeArray(schemaItem)) {
      const isItemTypeObject = isTypeObject(schemaItem.get('items'));
      if (isItemTypeObject) {
        schemaItem = schemaItem.update('items', updateSchemaItem);
      }
      return schemaItem.set('factory', schemaListFactory(schemaItem, isItemTypeObject));
    }

    return schemaItem;
  }

  function schemaListFactory(arraySchema = {}, isItemTypeObject = false) {

    return (values)=> {
      if (!(values instanceof Immutable.List)) {
        values = Immutable.List(isArray(values) ? values : undefined);
      }
      return values.update((list)=> {
        return list.map((listItem)=> {
          if (isItemTypeObject) {
            return arraySchema.getIn(['items', 'factory'])(listItem);
          }
          return listItem;
        });
      });
    };
  }

  return schema;
}
import Immutable from 'immutable';
import _ from 'lodash'
import jsen from 'jsen'

import isTypeArray from './../libs/isTypeArray';
import updateData from './../libs/updateData';
import completeSchema from './../libs/completeSchema';

function SchemaRecord(schema = {}) {

  if (!(this instanceof SchemaRecord)) {
    return new SchemaRecord(schema);
  }

  schema = Immutable.fromJS(schema);

  schema = completeSchema(schema);

  const DefinedRecord = Immutable.Record(updateData(schema), schema.id);

  function SchemaRecordType(values = {}) {
    DefinedRecord.call(this, updateData(schema, values));
  }

  SchemaRecordType.prototype = Object.create(DefinedRecord.prototype);
  SchemaRecordType.prototype.constructor = SchemaRecordType;

  SchemaRecordType.prototype._factories = schema.get('properties')
    .map((schemaItem)=> {
      return schemaItem.get('factory', null);
    })
    .toObject();


  SchemaRecordType.getSchema = SchemaRecordType.prototype.getSchema = function () {
    return schema;
  };

  SchemaRecordType.setSchema = SchemaRecordType.prototype.setSchema = function (newSchema) {
    schema = schema.merge(newSchema);
  };

  SchemaRecordType.createFactory = SchemaRecordType.prototype.createFactory = function (Model) {
    if (new Model() instanceof SchemaRecordType) {
      schema = schema.merge({'factory': SchemaRecord.createFactory(Model)});
    }
  };

  SchemaRecordType.prototype.set = function (key, val) {
    const factories = this._factories;
    const factory = factories && factories[key];
    return Object.getPrototypeOf(SchemaRecordType.prototype).set.call(this, key, factory ? factory(val) : val);
  };

  SchemaRecordType.getMeta = SchemaRecordType.prototype.getMeta = function (key) {
    return getMetaFromSchema(schema, key);
  };

  SchemaRecordType.getMetaIn = SchemaRecordType.prototype.getMetaIn = function (keyPath) {
    if (keyPath) {
      keyPath = [].concat(keyPath);
      return keyPath.reduce(getMetaFromSchema, schema);
    }
  };

  SchemaRecordType.prototype.validate = function () {
    return this.reduce((valid, value, key)=> {
      return this.getValidator(key)(value) && valid;
    }, true);
  };

  SchemaRecordType.prototype.setValidateError = function (key, errorKeyword) {
    const validate = this.getValidator(key);
    if (errorKeyword && validate) {
      const errors = validate.errors;
      errors.push({
        keyword: errorKeyword,
        path: ''
      });
      validate.errors = errors;
    }
    return this;
  };

  SchemaRecordType.prototype._validators = schema.get('properties')
    .map((schemaItem)=> {
      return createValidator(schemaItem)
    });

  SchemaRecordType.prototype.getValidator = function (key) {
    if (!this._validators.has(key)) {
      throw new Error(`${key} is not in ${String(this)}`)
    }
    return this._validators.get(key);
  };

  SchemaRecordType.prototype.getErrorMsg = function (key) {
    const validate = this.getValidator(key);

    if (validate.errors.length) {

      const errorMsg = this.getMeta(key).get('errorMsg', Immutable.fromJS({}));
      const displayErrorList = [];

      validate.errors.forEach((errorItem)=> {
        const msg = errorMsg.get(errorItem.keyword);
        if (msg) {
          displayErrorList.push(msg);
        }
      });

      if (displayErrorList.length) {
        return displayErrorList.join('; ')
      }

      return errorMsg.get('default') || '未知错误';
    }
    return null;
  };

  return SchemaRecordType;
}

SchemaRecord.createFactory = function (Model) {
  const factory = function (values) {
    return new Model(values);
  };
  factory.type = Model;
  return factory;
};

function getMetaFromSchema(schema, key) {
  if (key) {
    const fieldSchema = schema.getIn(['properties', key]);
    if (fieldSchema) {
      if (isTypeArray(fieldSchema)) {
        return getItemSchema(fieldSchema.get('items'));
      }
      return getItemSchema(fieldSchema);
    }
  }
  return schema;
}

function getItemSchema(itemSchema = {}) {
  if (itemSchema.has('factory')) {
    return itemSchema.get('factory').type.getMeta();
  }
  return itemSchema;
}

function createValidator(itemSchema = {}) {
  let options = {};

  if (itemSchema.has('formats')) {
    options.formats = itemSchema.get('formats').toObject();
    itemSchema = itemSchema.remove('formats');
  }

  const validate = jsen(itemSchema.toJS(), options);

  function customValidate(value) {

    validate(value);

    let errors = validate.errors;

    if (itemSchema.has('required')) {
      if (_.isEmpty(value)) {
        errors = [{
          'keyword': 'required',
          'path': ''
        }]
      }
    }

    return (customValidate.errors = errors) && errors.length === 0;
  }

  return customValidate;
}

export default SchemaRecord;