Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ When rendering a component that uses `@argument`, the initial value of the prope

In addition, any unexpected arguments to a component will also cause an error.

### Defining Types
## Defining Types

The `@argument` decorator takes a definition for what kind of value the property should be set to. Usually, this will represent the type of the value.

### Primitive Types

For primitives types, the name should be provided as a string (as with `string` in the example above). The available types match those of Typescript, including:

Expand Down Expand Up @@ -89,6 +93,28 @@ In addition, this library includes several predefined types for convenience:

These types can also be imported from `@ember-decorators/argument/types`

### Class Instances

The `@argument` decorator can also take a class constructor to validate that the property value is an instance of that class

```js
import Component from '@ember/component';
import { argument } from '@ember-decorators/argument';

class Task {
constructor() {
this.complete = false;
}
}

export default class TaskComponent extends Component {
@argument(Task)
task;
}
```

Passing a class works with all of the type helpers mentioned above.

## Installation

While `ember-decorators` is not a hard requirement to use this addon, it's recommended as it adds the base class field and decorator babel transforms
Expand Down
20 changes: 20 additions & 0 deletions addon/-private/combinators/and.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import BaseValidator from '../validators/-base';

export class AndValidator extends BaseValidator {
constructor(...validators) {
super();

this.validators = validators;
}

check(value) {
return this.validators.reduce(
(acc, validator) => acc && validator.check(value),
true
);
}
}

export default function and(...validators) {
return new AndValidator(...validators);
}
3 changes: 3 additions & 0 deletions addon/-private/combinators/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as and } from './and';
export { default as not } from './not';
export { default as or } from './or';
17 changes: 17 additions & 0 deletions addon/-private/combinators/not.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import BaseValidator from '../validators/-base';

export class NotValidator extends BaseValidator {
constructor(validator) {
super();

this.validator = validator;
}

check(value) {
return !this.validator.check(value);
}
}

export default function not(validator) {
return new NotValidator(validator);
}
17 changes: 17 additions & 0 deletions addon/-private/combinators/or.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import BaseValidator from '../validators/-base';

export class OrValidator extends BaseValidator {
constructor(...validators) {
super();

this.validators = validators;
}

check(value) {
return this.validators.some(validator => validator.check(value));
}
}

export default function or(...validators) {
return new OrValidator(...validators);
}
63 changes: 63 additions & 0 deletions addon/-private/resolve-validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { assert } from '@ember/debug';

import AnyValidator from './validators/any';
import BaseValidator from './validators/-base';
import InstanceOfValidator from './validators/instance-of';
import {
BOOLEAN as BOOLEAN_TYPE,
NUMBER as NUMBER_TYPE,
STRING as STRING_TYPE,
SYMBOL as SYMBOL_TYPE
} from './validators/type-match';
import {
NULL as NULL_VALUE,
UNDEFINED as UNDEFINED_VALUE
} from './validators/value-match';
import { not, or } from './combinators/index';

const primitiveTypeValidators = {
any: new AnyValidator('any'),
object: not(
or(
BOOLEAN_TYPE,
NUMBER_TYPE,
STRING_TYPE,
SYMBOL_TYPE,
NULL_VALUE,
UNDEFINED_VALUE
)
),

boolean: BOOLEAN_TYPE,
number: NUMBER_TYPE,
string: STRING_TYPE,
symbol: SYMBOL_TYPE,

null: NULL_VALUE,
undefined: UNDEFINED_VALUE
};

export default function resolveValidator(type) {
if (type === null) {
return NULL_VALUE;
} else if (type === undefined) {
return UNDEFINED_VALUE;
} else if (type instanceof BaseValidator) {
return type;
} else if (typeof type === 'function' || typeof type === 'object') {
// We allow objects for certain classes in IE, like Element, which have typeof 'object' for some reason
return new InstanceOfValidator(type);
} else if (typeof type === 'string') {
assert(
`Unknown primitive type received: ${type}`,
primitiveTypeValidators[type] !== undefined
);

return primitiveTypeValidators[type];
} else {
assert(
`Types must either be a primitive type string, class, validator, or null or undefined, received: ${type}`,
false
);
}
}
26 changes: 21 additions & 5 deletions addon/-private/types/array-of.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { assert } from '@ember/debug';
import { resolveValidator, makeValidator } from '../validators';
import { isArray } from '@ember/array';
import { assert } from '@ember/debug';

import resolveValidator from '../resolve-validator';
import BaseValidator from '../validators/-base';

class ArrayOfValidator extends BaseValidator {
constructor(validator) {
super();

this.validator = validator;
}

toString() {
return `arrayOf(${this.validator})`;
}

check(value) {
return isArray(value) && value.every(value => this.validator.check(value));
}
}

export default function arrayOf(type) {
assert(
Expand All @@ -10,7 +28,5 @@ export default function arrayOf(type) {

const validator = resolveValidator(type);

return makeValidator(`arrayOf(${validator})`, value => {
return isArray(value) && value.every(validator);
});
return new ArrayOfValidator(validator);
}
16 changes: 12 additions & 4 deletions addon/-private/types/one-of.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { assert } from '@ember/debug';
import { makeValidator } from '../validators';

import { OrValidator } from '../combinators/or';
import ValueMatchValidator from '../validators/value-match';

class OneOfValidator extends OrValidator {
toString() {
return `oneOf(${this.validators.join()})`;
}
}

export default function oneOf(...list) {
assert(
Expand All @@ -11,7 +19,7 @@ export default function oneOf(...list) {
list.every(item => typeof item === 'string')
);

return makeValidator(`oneOf(${list.join()})`, value => {
return list.includes(value);
});
const validators = list.map(value => new ValueMatchValidator(value));

return new OneOfValidator(...validators);
}
32 changes: 21 additions & 11 deletions addon/-private/types/optional.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { assert } from '@ember/debug';
import { resolveValidator, makeValidator } from '../validators';

const nullValidator = resolveValidator(null);
const undefinedValidator = resolveValidator(undefined);
import resolveValidator from '../resolve-validator';
import {
NULL as NULL_VALUE,
UNDEFINED as UNDEFINED_VALUE
} from '../validators/value-match';
import { OrValidator } from '../combinators/or';

class OptionalValidator extends OrValidator {
constructor(validator) {
super(NULL_VALUE, UNDEFINED_VALUE, validator);

this.originalValidator = validator;
}

toString() {
return `optional(${this.originalValidator})`;
}
}

export default function optional(type) {
assert(
Expand All @@ -11,20 +26,15 @@ export default function optional(type) {
);

const validator = resolveValidator(type);
const validatorDesc = validator.toString();

assert(
`Passsing 'null' to the 'optional' helper does not make sense.`,
validatorDesc !== 'null'
validator.toString() !== 'null'
);
assert(
`Passsing 'undefined' to the 'optional' helper does not make sense.`,
validatorDesc !== 'undefined'
validator.toString() !== 'undefined'
);

return makeValidator(
`optional(${validator})`,
value =>
nullValidator(value) || undefinedValidator(value) || validator(value)
);
return new OptionalValidator(validator);
}
51 changes: 33 additions & 18 deletions addon/-private/types/shape-of.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
import { assert } from '@ember/debug';
import { get } from '@ember/object';
import { resolveValidator, makeValidator } from '../validators';

import resolveValidator from '../resolve-validator';
import BaseValidator from '../validators/-base';

class ShapeOfValidator extends BaseValidator {
constructor(shape) {
super();

this.shape = {};
this.typeDesc = [];

for (let key in shape) {
this.shape[key] = resolveValidator(shape[key]);

this.typeDesc.push(`${key}:${shape[key]}`);
}
}

toString() {
return `shapeOf({${this.typeDesc.join()}})`;
}

check(value) {
for (let key in this.shape) {
if (this.shape[key].check(get(value, key)) !== true) {
return false;
}
}

return true;
}
}

export default function shapeOf(shape) {
assert(
Expand All @@ -16,21 +47,5 @@ export default function shapeOf(shape) {
Object.keys(shape).length > 0
);

let typeDesc = [];

for (let key in shape) {
shape[key] = resolveValidator(shape[key]);

typeDesc.push(`${key}:${shape[key]}`);
}

return makeValidator(`shapeOf({${typeDesc.join()}})`, value => {
for (let key in shape) {
if (shape[key](get(value, key)) !== true) {
return false;
}
}

return true;
});
return new ShapeOfValidator(shape);
}
14 changes: 10 additions & 4 deletions addon/-private/types/union-of.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { assert } from '@ember/debug';
import { resolveValidator, makeValidator } from '../validators';

import resolveValidator from '../resolve-validator';
import { OrValidator } from '../combinators/or';

class UnionOfValidator extends OrValidator {
toString() {
return `unionOf(${this.validators.join()})`;
}
}

export default function unionOf(...types) {
assert(
Expand All @@ -9,7 +17,5 @@ export default function unionOf(...types) {

const validators = types.map(resolveValidator);

return makeValidator(`unionOf(${validators.join()})`, value => {
return validators.some(validator => validator(value));
});
return new UnionOfValidator(...validators);
}
Loading