Skip to content

Commit

Permalink
Merge pull request #44 from reyx7/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
nsoufian authored Jan 30, 2019
2 parents 4f3ab59 + 45ef656 commit c3a5522
Show file tree
Hide file tree
Showing 27 changed files with 2,314 additions and 453 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
key: v1-dependencies-{{ checksum "package.json" }}

# run tests!
- run: yarn test --coverage
- run: yarn test --coverage --verbose

# run linting
- run: yarn lint
Expand Down
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
},
"overrides": [
{
"files": ["*.test.js"],
"files": ["*.test.js", "utils.js"],
"rules": {
"no-unused-expressions": "off",
"no-undef": "off"
"no-undef": "off",
"no-eval": "off"
}
}
]
Expand Down
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
{
"name": "v4f",
"version": "0.0.1",
"description": "Light and Simple data & types validator for browser from",
"main": "lib/index",
"version": "0.0.9",
"description": "A declarative, efficient, and flexible JavaScript validation library for Humans .",
"maintainers": [
"soufiane nassih"
],
"main": "lib/cjs.js",
"module": "lib/es.js",
"typings": "lib/index.d.ts",
"author": "Nassih soufiane",
"files": [
"lib"
],
"license": "MIT",
"dependencies": {},
"devDependencies": {
Expand Down
43 changes: 31 additions & 12 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import resolve from "rollup-plugin-node-resolve";
import babel from "rollup-plugin-babel";

export default {
input: "src/index.js",
output: {
file: "lib/index.js",
format: "cjs"
export default [
// CommonJS
{
input: "src/index.js",
output: {
file: "lib/cjs.js",
format: "cjs",
indent: false
},
plugins: [
resolve(),
babel({
exclude: "node_modules/**" // only transpile our source code
})
]
},
plugins: [
resolve(),
babel({
exclude: "node_modules/**" // only transpile our source code
})
]
};
// ES
{
input: "src/index.js",
output: {
file: "lib/es.js",
format: "es",
indent: false
},
plugins: [
resolve(),
babel({
exclude: "node_modules/**" // only transpile our source code
})
]
}
];
83 changes: 64 additions & 19 deletions src/field.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,76 @@
import { getErrorMessage, fieldWrapper } from "./utils";
import rules from "./rules/index";
import allRules from "./rules/index";
import { rulesWrapper, resolveArgs, isOptionalSuccess } from "./utils";

class Field {
#rules = [];
const isFail = (isRuleSuccess, constraint, strict, values) =>
strict
? constraint && constraint.validate(values) !== isRuleSuccess
: constraint && constraint.validate(values) && !isRuleSuccess;

_add(rule) {
// push this rule given in param to rules array
this.#rules.push(rule);
// and return context to continue the chain of rules
const messageTemplate = (message, value, field) =>
message
.replace("%{value}", value)
.replace("%{field}", field)
.trim();

export class Field {
#rules = null;

#not = null;

constructor(rules = [], not = false) {
this.#rules = rules;
this.#not = not;
}

_clone() {
return [this.#rules, this.#not];
}

get any() {
return this;
}

validate(value, { message = false } = {}) {
get not() {
return new Field([...this.#rules], true);
}

validate(
value,
{ verbose = false, values = {}, strict = true, field = "" } = {}
) {
for (let i = 0; i < this.#rules.length; i += 1) {
// Get validator function and options object from
// the current rule.
const { validator, options } = this.#rules[i];
if (validator(value) !== true) {
// the rule is fail , we check if the user want
// a error message indicator or boolean.
if (message === true) return getErrorMessage(options);
return false;
const {
name,
rule,
args,
not,
options: { constraint, message }
} = this.#rules[i];

const isRuleSuccess = not !== rule(...resolveArgs(args, values), value);

const isOptionalRule =
name === "required" &&
(not || (strict && constraint && !constraint.validate(values)));

if (isOptionalRule) {
if (isOptionalSuccess(value, this.#rules[i + 1].name)) {
break;
}
} else if (
(!isRuleSuccess && !constraint) ||
(name === "required" &&
(!not && strict && constraint) &&
!isRuleSuccess) ||
isFail(isRuleSuccess, constraint, strict, values)
) {
return verbose === true
? messageTemplate(message, value, field)
: false;
}
}
// All rules are valide we return true to indicate that
return true;
}
}

export default fieldWrapper(rules)(Field);
export default rulesWrapper(allRules)(Field);
158 changes: 120 additions & 38 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,135 @@
type Options = {
message: string;
};
interface Options {
message?: string;
constraint?: (target: "#" | ["#", "#"], condition: Field) => any;
}

type Rules = {
required(options: Options): Rules;
equals(value: any, options: Options): Rules;
};
type RelatedField = ["#", Function];

type Rules<T, R = T> = {
required(options?: Options): Rules<T, R>;
not: Rules<T, R>;
equals(value: any | RelatedField, options?: Options): Rules<T, R>;
exact(value: any | RelatedField, options?: Options): Rules<T, R>;
none(options?: Options): Rules<T, R>;
empty(options?: Options): Rules<T, R>;
oneOf(
value: Array<any> | Object | RelatedField,
options?: Options
): Rules<T, R>;
exactOneOf(
value: Array<any> | Object | RelatedField,
options?: Options
): Rules<T, R>;
} & T &
R;

type IteratorRules<T = StringRules | ArrayRules> = {
min(value: Number, options: Options): IteratorRules<T> & Rules & T;
max(value: Number, options: Options): IteratorRules<T> & Rules & T;
lengthEquals(value: Number, options: Options): IteratorRules<T> & Rules & T;
lengthEquals(value: Number | RelatedField, options?: Options): IT<T>;
lengthLess(value: Number | RelatedField, options?: Options): IT<T>;
lengthLessOrEquals(value: Number | RelatedField, options?: Options): IT<T>;
max(value: Number | RelatedField, options?: Options): IT<T>;
lengthGreater(value: Number | RelatedField, options?: Options): IT<T>;
lengthGreaterOrEquals(value: Number | RelatedField, options?: Options): IT<T>;
min(value: Number | RelatedField, options?: Options): IT<T>;
lengthBetween(
min: Number,
max: Number,
options: Options
): IteratorRules<T> & Rules & T;
min: Number | RelatedField,
max: Number | RelatedField,
options?: Options
): IT<T>;
lengthBetweenOrEquals(
min: Number | RelatedField,
max: Number | RelatedField,
options?: Options
): IT<T>;
} & T;

type StringRules = {
first(value: String | RelatedField, options?: Options): STRING;
last(value: String | RelatedField, options?: Options): STRING;
last(value: String | RelatedField, options?: Options): STRING;
pattern(value: String | RelatedField, options?: Options): STRING;
ipv4(value: String | RelatedField, options?: Options): STRING;
ipv6(value: String | RelatedField, options?: Options): STRING;
ip(value: String | RelatedField, options?: Options): STRING;
url(value: String | RelatedField, options?: Options): STRING;
alpha(value: String | RelatedField, options?: Options): STRING;
alphaNum(value: String | RelatedField, options?: Options): STRING;
num(value: String | RelatedField, options?: Options): STRING;
bool(value: String | RelatedField, options?: Options): STRING;
domain(value: String | RelatedField, options?: Options): STRING;
host(value: String | RelatedField, options?: Options): STRING;
email(value: String | RelatedField, options?: Options): STRING;
};

type NumberRules = {
between(min: Number, max: Number, options: Options): NumberRules & Rules;
less(value: Number | RelatedField, options?: Options): NUBMER;
lessOrEquals(value: Number | RelatedField, options?: Options): NUBMER;
greater(value: Number | RelatedField, options?: Options): NUBMER;
greaterOrEquals(value: Number | RelatedField, options?: Options): NUBMER;
positive(value: Number | RelatedField, options?: Options): NUBMER;
negative(value: Number | RelatedField, options?: Options): NUBMER;
between(
min: Number | RelatedField,
max: Number | RelatedField,
options?: Options
): NUBMER;
betweenOrEquals(
min: Number | RelatedField,
max: Number | RelatedField,
options?: Options
): NUBMER;
};

type StringRules = {
startsWith(
startValue: String,
options: Options
): StringRules & Rules & IteratorRules<StringRules>;
endsWith(
endValue: String,
options: Options
): StringRules & Rules & IteratorRules<StringRules>;
type ArrayRules = {
allEquals(value: any | RelatedField, options?: Options): ARRAY;
allExact(value: any | RelatedField, options?: Options): ARRAY;
};

type ArrayRules = {};

type NumberRules = {
between(min: Number, max: Number, options: Options): NumberRules & Rules;
type BooleanRules = {
falsy(options?: Options): BOOLEAN;
truthy(options?: Options): BOOLEAN;
};

type BaseRules = {
string(options: Options): StringRules & IteratorRules<StringRules> & Rules;
number(options: Options): Rules;
boolean(options: Options): Rules;
object(options: Options): Rules;
};
type TypeSchema = {
validate(values: Object, options: Options): Object | Boolean;
type ObjectRules = {
hasKey(value: String, options?: Options): OBJECT;
hasValue(value: any, options?: Options): OBJECT;
};

export function field(): BaseRules;
export function typeSchema(schema: Object): BaseRules;
type STRING = StringRules &
IT<StringRules> &
Rules<StringRules, IT<StringRules>>;

type NUBMER = NumberRules & Rules<NumberRules>;

type BOOLEAN = BooleanRules & Rules<BooleanRules>;

type OBJECT = ObjectRules & Rules<ObjectRules>;

type ARRAY = ArrayRules & IT<ArrayRules> & Rules<ArrayRules, IT<ArrayRules>>;

type IT<T> = IteratorRules<T> & Rules<T, IteratorRules<T>>;

interface Field {
string(options?: Options): STRING;
number(options?: Options): NUBMER;
boolean(options?: Options): BOOLEAN;
array(options?: Options): ARRAY;
object(options?: Options): OBJECT;
any: STRING & NUBMER & BOOLEAN & ARRAY & OBJECT;
}

interface V<T = any> {
validate(
values: Object,
options?: { verbose: false; strict: true; bool: false; async: false }
): Object | Boolean;
}

export function Field(): Field;

export function Schema<T = Object>(
schema: T,
options?: { verbose: false; strict: true; bool: false; async: false }
): V & { [K in keyof T]: V<T> };

export function When(target: "#" | ["#", "#"], condition: Field): any;
11 changes: 7 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Field from "./field";
import Schema from "./schema";
import FieldObject from "./field";
import SchemaObject from "./schema";
import WhenObject from "./when";

export const field = () => new Field();
export const Field = () => new FieldObject();

export const typeSchema = Schema;
export const When = (name, rule) => new WhenObject([{ name, rule }]);

export const Schema = SchemaObject;
21 changes: 21 additions & 0 deletions src/rules/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { equals, exact } from "./generic";

export const array = v => v instanceof Array;

export const allEquals = (value, arr) => {
for (let i = 0; i < arr.length; i += 1) {
if (!equals(value, arr[i])) {
return false;
}
}
return true;
};

export const allExact = (value, arr) => {
for (let i = 0; i < arr.length; i += 1) {
if (!exact(value, arr[i])) {
return false;
}
}
return true;
};
Loading

0 comments on commit c3a5522

Please sign in to comment.