Git Product home page Git Product logo

speck's Introduction

Speck

Speck - Let you create your domain entities with reactive validation based on React propTypes

Build Status

This package let you create entities with schema validation based on React PropTypes.

Installing

$ npm install speck-entity

Using

Sample Entities

const Joi = require('joi')
const joiAdapter = require('validatorAdapters')('joi', Joi)
const Speck = require('speck-entity')

class MyEntity extends Speck {
  static SCHEMA = {
    field: joiAdapter(Joi.string()),
    otherField: {
      validator: joiAdapter(Joi.number()),
      defaultValue: 10
    }
  }
}

class FatherEntity extends Speck {
  static SCHEMA = {
    children: {
      validator: joiAdapter(Joi.array().items(Joi.object().type(MyEntity)))
      type: MyEntity
    }
  }
}

Get default values

const niceInstance = new MyEntity();
console.log(niceInstance.toJSON()); // { field: undefined, otherField: 10 }
console.log(niceInstance.errors); // {}

Validations

const buggedInstance = new MyEntity({ field: 10, otherField: 'value' });
console.log(buggedInstance.toJSON()); // { field: 10, otherField: 'value' }
console.log(buggedInstance.errors); /* or buggedInstance.getErrors() -- but... getErrors also includes children errors
  {
    field: {
      errors: [ 'Invalid undefined `field` of type `number` supplied to `MyEntityEntity`, expected `string`.' ]
    },
    otherField: {
      errors: [ 'Invalid undefined `otherField` of type `string` supplied to `MyEntityEntity`, expected `number`.' ]
    }
  }
*/

Validate on change value

const otherInstance = new MyEntity({ field: 'myString' });
console.log(otherInstance.errors); // {}
console.log(otherInstance.valid); // true

otherInstance.field = 1;
console.log(otherInstance.errors); // {field: { errors: [ 'Invalid undefined `field` of type `number` supplied to `MyEntityEntity`, expected `string`.' ] }}
console.log(otherInstance.valid); // false

Parse children to Entity

const fatherInstance = new FatherEntity({
  children: [{
    field: 'A',
    otherField: 2
  }, {
    field: 'B',
    otherField: 3
  }]  
})
console.log(fatherInstance.children[0]); //An instance of MyEntity
console.log(fatherInstance.children[1].toJSON());
//{ field: 'B', otherField: 3 }

Builder

When you need to create objects with custom verification like

    const elementList = {
        elements: [{
          type: 'product',
          name: true,
          price:  true
        }, {
          type: 'default',
          isDefault: true
        }]
      };

In such cases you can define a builder as follows:

class ElementList extends Speck {}
ElementList.SCHEMA = {
  elements: {
    validator: noop,
    builder: (dataList, Type, dependencies) => dataList.map(data => {
      if (data.type === 'product') return new ProductEntity(data, dependencies);
      if (data.type === 'default') return new FakeEntityWithBoolean(data);
    })
  }
};

And use it like:

new ElementList(elementList, someDependency)

(note that you can pass custom dependencies to your child entities and latter access them on the builder)

By defining builder you tell Speck Entity that you take the responsibility of instansitating and returning a new object of the type which suits you the best. This is a powerful concept as it lets users dynamically create new types on the fly.

Clean unexpected values

const anotherInstance = new MyEntity({ field: 'myString', fake: 'fake' });
console.log(anotherInstance.toJSON()); // { field: 'myString', otherField: 10 }

To understand the validators React PropTypes

Well known issues

  • Create helpers for relationships validations(Like, mininum, maximum)
  • Create identifier and equal comparison
  • Type builders and/or custom builders are not being applied on instance setters

Contextual validation

  class FakeEntityWithExcludeContext extends Speck {
      static SCHEMA = {
          id: joiAdapter(Joi.number().required()),
          requiredProp1: joiAdapter(Joi.number().required()),
          requiredProp2: joiAdapter(Joi.number().required()),
          requiredProp3: joiAdapter(Joi.number().required())
      }

      static CONTEXTS = {
        create: { exclude: [ 'requiredProp2', 'requiredProp3' ] },
        edit: { include: [ 'id', 'requiredProp1', 'requiredProp2' ] },
        onlyId: { include: [ 'id' ] }        
      }
  }

   const myEntity = new FakeEntityWithIncludeContext({ id: 1 });

   const contextCreate = myEntity.validateContext('create');
   console.log(contextCreate.errors); // { requiredProp1: { errors: [ ... ] } }
   console.log(contextCreate.valid); // false

   const contextEdit = myEntity.validateContext('edit');
   console.log(contextEdit.errors); // { requiredProp1: { errors: [ ... ] }, requiredProp2: { errors: [ ... ] } }
   console.log(contextEdit.valid); // false

   const contextOnlyId = myEntity.validateContext('onlyId')
   console.log(contextOnlyId.errors); // {}
   console.log(contextOnlyId.valid); // true

Each context (create and edit in example above), could have include property OR exclude, the include property receives the properties that will be validated in this context, and the exclude property represents the properties that will be ignored on validation.

In the example, the create context, will only check the 'requiredProp1' and 'requiredProp2' fields, and the edit context will check 'requiredProp1', 'requiredProp2' and 'id' properties.

You can't combine include and exclude in the same context definition

##Custom validation You can validate your entity adding the property in fields and setting the new validator

  class Entity extends Speck {
    static SCHEMA = {
      id: joiAdapter(Joi.number().required()),
      requiredProp1: joiAdapter(Joi.number().required())
    }

    static CONTEXTS = {
      create: {
        fields: {
          requiredProp1: (obj, field) => {
            if(obj[field] === -1) return new Error('Error -1');
          }
        }
      }
    }
  }

  const entity = new Entity({
    id: 1,
    requiredProp1: -1
  });

  const contextValidated = entity.validateContext('create');
  console.log(entity.errors.requiredProp1); // undefined
  console.log(contextValidated.requiredProp1); // { errors: [Error: Error -1] }

Hooks

class EntityWithHook extends Speck {
    static SCHEMA = {
        fieldWithHook: {
          validator: joiAdapter(Joi.number()),
          hooks: {
            afterSet(data, fieldName) {
              // data is the whole data of the instance
              // fieldName the current fieldName
              // DO WHATEVER YOU WANT
            }
          }
        },
        anotherFieldWithHook: {
          validator: joiAdapter(Joi.number()),
          hooks: {
            afterSet(data, fieldName) {
              return { anotherField: data[fieldName] * 2 } // if the afterSet hook returns an object is merged to data
            }
          }
        },
        anotherField: joiAdapter(Joi.number()),
    }
}

const myEntity = new EntityWithHook({ fieldWithHook: 'foo', anotherFieldWithHook: 'bar', anotherField: null });

myEntity.anotherFieldWithHook = 10 //according to the after set hook anotherField newValue will be 20

speck's People

Contributors

agutoli avatar albertossilva avatar dalssoft avatar guisouza avatar mvcds avatar pnkapadia64 avatar sanyamagrawal avatar vicentelyrio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

speck's Issues

Apply entity builder on setter.

Guys I really hope I am wrong, but i think we are not applying entity builders on setter, but only on entity construction ..

Is this the expected behaviour ? or it is a bug ?

@albertossilva could you please check, i have a fix for it, like this :

    set: function (value){
      if (instance.schema[field].type || instance.schema[field].builder) {
        value = instance.applyEntityConstructor(instance.schema[field], value);
      }
      if(instance.data[field] !== value) {
        instance.data[field] = value;
        return instance._validate();
      }
    },

should I submit the PR ?

Doesn't show valid attribute

The errors object should has a valid attribute.
Eg.:

  static SCHEMA = {
    title: titleValidator
  }


function titleValidator(obj, field) {
  if (!obj[field]) {
    return new Error("Title can't be empty");
  }
}

Response
Entity = { "errors":{ "title":{ "errors":["Title can't be empty"] }, "valid":false }

Expected
Entity = { "errors":{ "title":{ "errors":["Title can't be empty"], "valid":false }, "valid":false }

Weird behaviour on toJSON method

When you fill a child element with an invalid value toJSON method acts in a weird way. The parent toJSON returns the old child element and the toJSON of the child element returns the child element in the invalid state.

For example, given the schema:

class MyEntity extends Speck {
  static SCHEMA = {
    field: PropTypes.string,
    anotherField: PropTypes.string
  }
}

class FatherEntity extends Speck {
  static SCHEMA = {
    child: PropTypes.instanceOf(MyEntity)
  }
}

And the variables setup:

const a = new FatherEntity({ child: { field: 'value' } })
a.child = { field: 'invalid entity', anotherField: 'yet a invalid entity' }

The toJSON method have this behaviour:

//prints { child: { field: 'value' } }
console.log(a.toJSON()) 
//prints { field: 'invalid entity', anotherField: 'yet a invalid entity' }
console.log(a.child.toJSON()) 

Should customize error message

When a entity is not valid, the error property is:

{ property1: { errors: [ 'Required undefined `property1` was not specified in `FakeEntity`.' ] } }

But the error message is not friendly to application user.

I suggest to add a new param in validation:

class FakeEntity extends Speck {
    static SCHEMA = {
        property1: {
            validator: Proptypes.number.isRequired,
            errorMessage: 'Property1 must be a number'
        }
    }
}

So, the error message will be:

{ property1: { errors: ['Property1 must be a number' ] } }

Fields cannot be freely named

When I try to create an entity like this...

export default class Foo extends Entity {
  static SCHEMA = {
    id: PropTypes.string,
    expression: {
      validator: PropTypes.string,
      defaultValue: ''
    }
  };
};

...the instantiated object contains the field expresion, but it is an object with two properties: expression (empty string) and errors (false).

Renaming the field to anything else caused it to behave as expected.

The same happens if I name the field as value. It gets two properties: expression (empty string) and errors (false).

Unfriendly Error label .

Hi everyone!
In the last days I started implementing Speck Entities from scratch after some months at @RevMob and i think its very unfriendly on getting started, with some weird errors, so I created the unfriendly error label and I will start to create some issues under it, to suggest some more friendly errors, after some suggestions and discussions I could create a PR for it .

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.