gcanti / tcomb-validation Goto Github PK
View Code? Open in Web Editor NEWValidation library based on type combinators
License: MIT License
Validation library based on type combinators
License: MIT License
Are there any plans to allow custom validation errors on a subtype? I've got the following subtype:
t.subtype(t.Str, function (string) {
try {
JSON.parse(string);
return true;
} catch (e) {
// surface e.message?
return false;
}
}, "JSON Schema");
It's currently possible to customize the messaging for a particular property:
var Identifier = subtype(Str, function (v) {
return /^[a-z._-]{1,32}$/.test(v);
}, 'Identifier');
var Thing = struct({
id: Identifier
});
validate(Thing, {id: 'lol invalid'}, {
messages: {
id: ':path invalid. :actual must be a string containing only alphanumeric characters, hyphens, underscores and dots, and be between 1 and 32 characters long.'
}
});
But this gets awkward when you re-use the same type in multiple places:
var RelatedThing = Thing.extend({ parentId: Identifer });
Instead of repeating the same message (or putting it into a variable and repeating that), It would be much DRYer to define messages for types/predicates instead of fields. This would also give the advantage that one can give more specific error messages:
validate(RelatedThing, {id: 'totally_valid', parentId: 'not so valid'}, {
typeMessages: {
Str: ':actual at :path must be a string.',
Identifier: ':actual at :path must contain 1-32 alphanumeric characters, hyphens, underscores or dots.'
}
});
What do you think?
I can't figure out how to validate that a field should be a non-empty list of structs. Is that possible?
Proposal: an optional getValidationErrorMessage(value, path)
on the type constructor:
var ShortString = t.subtype(t.String, function (s) {
return s.length < 3;
});
ShortString.getValidationErrorMessage = function (value) {
if (!value) {
return 'Required';
}
if (s.length >= 3) {
return 'Too long my friend';
}
};
validate('abc', ShortString).firstError().message; // => 'Too long my friend'
How to keep DRY?
In order to keep the validation logic in one place, one may define a custom combinator:
function mysubtype(type, getValidationErrorMessage, name) {
var Subtype = t.subtype(type, function (x) {
return !t.String.is(getValidationErrorMessage(x));
}, name);
Subtype.getValidationErrorMessage = getValidationErrorMessage;
return Subtype;
}
var ShortString = mysubtype(t.String, function (s) {
if (!value) {
return 'Required';
}
if (s.length >= 3) {
return 'Too long my friend';
}
});
Surprisingly, most samples do not deal with Date. I have an object that I want to validate the date with. The object I want to supply is:
{ dateTimeGiven: "2001-01-01" } and my struct is:
const newType = t.struct ({ dateTimeGiven: t.Date});
I am getting invalid date for every date that I supply. Can you provide a direct sample of how to validate a date given in a json object? Thanks.
Jim
I'm a bit confused about the RegExp type shown in the readme. Normally you would want to test a field against a RegExp pattern, e.g. check for a valid email, rather than check that a field is a RegExp pattern (as shown in the example). Would you have to create a refinement for the former use case of checking a field against a pattern? Why would you want a RegExp type in tcomb otherwise? What would it be used for?
I created library with flow types and published it using: https://github.com/AgentME/flow-copy-source so they are available in files *.js.flow And now t-comb can't see those types so I can't do proper validation on post payload.
Hi 👋 big fan of tcomb and just started exploring this lib for validation.
Had a question when I have this setup:
const betterString = t.refinement(t.String, x => x !== '', 'BetterString')
const State = t.struct({
state: betterString,
code: t.maybe(t.Number)
}, {name:'State'})
const Code = t.struct({
code: t.Number,
state: t.maybe(t.String)
}, {name:'Code'})
const Union = t.union([State, Code], 'StateOrCode')
Union.dispatch = (obj) => {
return betterString.is(obj['state']) ? State : Code
}
const data = {
'32897284': {
state: '',
code: null
}
}
which basically i want either state or code to be filled, both being filled is ok as well otherwise its an erorr
everything works great with this setup however in the errors.path
i get the following ['32897284', 1, 'code']
not sure
why i need that number 1
in there.
i tracked the code down to Line 223 which concats an index with the paths
but im not sure i follow the logic for this to be in the paths array and was hoping for some insight
is this a bug or works as expected?
The version of tcomb being used by the playground seems quite old, in particular there is no default dispatch defined for Union types.
For some reason tcomb-validation
outputs different from tcomb
error message — it doesn't account for name
option of t.struct
.
Example:
const Schema = t.struct({
name: t.String,
surname: t.maybe(t.String),
age: t.Integer,
tags: t.list(t.String)
}, { name: 'Person', strict: true })
Schema(data)
console.log(tv.validate(data, Schema).errors)
tcomb
will produce
[tcomb] Invalid value undefined supplied to Person/name: String
tcomb-validation
will produce
Invalid value undefined supplied to /name: String
Note how tcomb
prints Person
as name from struct
, but tcomb-validate
does not.
Is it per design?
var ShortString = t.subtype(t.String, function (s) {
return s.length < 3;
});
ShortString.getValidationErrorMessage = function (value) {
if (!value) {
return 'Required';
}
if (value.length >= 3) {
return 'Too long my friend';
}
};
validate(undefined, ShortString) // returns the default error message instead of the custom one.
I am using version 3.2.2 and I am trying to build a library of reusable validators around tcomb-validator.
So I first defined my custom field validators which are predicates and then wanted to compose an object validator using the structure like follow:
` var Title = function (s) {
return s.length > 2 && s.length <= 255;
}`
`Title.getValidationErrorMessage = function (value) {
if (!value) {
return 'document.validation.error.missing';
}
if (value.length <= 2) {
return 'document.validation.error.tooShort';
}
if (payload.title.length > 255) {
return 'document.validation.error.tooLong';
}
};`
And then used like this:
`var BaseDocument = t.struct({
title: Title,
body: OtherCustomType,
});
var payload = {
title: "abcd",
body: "some other valid value",
};
var validationResult = validate(payload, BaseDocument);`
But in this case, even if the payload is valid and the validationResult.isValid() returns true
Nice idea I like it but the API is too difficult to use.
I want to have assert throw a useful error I don't think having people download another npm package to get sensible feedback as to why asserts are failing makes sense.
var VinylFile = tcomb.struct({
cwd: tcomb.Str,
path: tcomb.Str,
base: tcomb.Str
});
tcomb.assert(VinylFile.is(myVinylFileInstance)); //This should throw an error with the first error as it's message not "assert failed".
assert failed
is useless as a message. I'm trying to use tcomb in a current project and frankly it's not giving me the quick and clean feedback I would expect it to.
Merge this with tcomb and have assert throw good errors so I don't have to launch node-inspector.
The lines of code required by someone using this library should be
If an assert fails the error needs to be excellent and tell the developer exactly why it failed.
Example to reproduce (in the playground):
var Dict = t.dict(Str, Str);
validate({a: "a", b: "b", c: "c", d: 0}, Dict);
Error message:
/a/b/c/d is
0
should be aStr
I'd expect it to just be:
/d is
0
should be aStr
Hi,
So, following this, I guess the next logical step would be to provide typescript definitions for tcomb-validation. Would it be easier to implement?
Hi and thanks for the great lib!
I have one question though: what is the proper way to return localized error messages from getValidationErrorMessage
function?
The problem is I have to pass user's locale into this function somehow in order to return proper error message and the only solution I found now is to construct one struct per locale (but I don't like it):
// ./structs/ShortString.js
const _ = require('lodash');
const t = require('tcomb');
const intl = require('./intl');
module.exports = _.memoize(locale => {
const ShortString = t.subtype(t.String, s => s.length < 3);
ShortString.getValidationErrorMessage = value => {
if (!value) {
return intl(locale, 'Required');
}
if (value.length >= 3) {
return intl(locale, 'Too long my friend');
}
};
return ShortString;
});
// Some Express.js middleware
const ShortString = require('./structs/ShortString');
app.use((req, res) => {
const locale = getUserLocale(req);
const validationResult = validate(req.query.str, ShortString(locale));
});
The other solution could be is to pass an external options
(or context
) argument to validate()
.
Then this example would look like this:
// ./structs/ShortString.js
const _ = require('lodash');
const t = require('tcomb');
const intl = require('./intl');
const ShortString = t.subtype(t.String, s => s.length < 3);
ShortString.getValidationErrorMessage = (value, context) => {
const { locale } = context;
if (!value) {
return intl(locale, 'Required');
}
if (value.length >= 3) {
return intl(locale, 'Too long my friend');
}
};
module.exports = ShortString;
// Some Express.js middleware
const ShortString = require('./structs/ShortString');
app.use((req, res) => {
const locale = getUserLocale(req);
const validationResult = validate(req.query.str, ShortString, { locale });
});
What do you think about it?
I'd like the ability to set a default error message in tcomb-validation for certain scenarios. For example, currently the default message that tcomb-validation gives you is 'Invalid value ' + stringify(actual) + ' supplied to ' + to;
Which is fine. But I'd like all required fields to say to + ' is required'
without having to specify it in each field. I'm using this w/ tcomb-form and I can set the error message in each field, but it would be much nicer if I could just have all required fields use a default for that condition...
The "solution" to keep check and related error message dry looks more like a workaround.
Issues:
getValidationErrorMessage
is called. One candidate for a non-identical world state is e.g. a new Date()
.Instead, the check should be able to leave check (failure) details which are provided to (e.g.) getValidationErrorMessage
.
should this be working?
const data = {
text: 'whyoff'
};
const attributes = t.union([
t.struct({ text: t.String }),
t.struct({ url: t.String })
], 'Attributes');
const result = validate(data, attributes);
console.log(result);
=========================================
Struct {
errors:
[ Struct {
message: 'Invalid value {\n "text": "whyoff"\n} supplied to Attributes',
actual: [Object],
expected: [Object],
path: [] } ],
value: { text: 'whyoff' } }
Excuse me but I'm a little bit puzzled about the need of tcomb-validation
, what's the gain of this:
const tv = require('tcomb-validation');
tv.validate(1, tv.String).isValid(); // => false
tv.validate('a', tv.String).isValid(); // => true
Versus this:
const t = require('tcomb');
const aString = t.String(1);
const bString = t.String('a');
Wasn't the whole point of tcomb
to have an identity function which did validation in the background?
With tcomb-validation
now I have to add boilerplate code to check every single thing.
Please, don't misunderstand me, I'm not trolling, it's I'm not seeing the benefit, but since someone took the time to make tcomb-validation
, then I guess I'm missing something.
hi, when I use restify create an API, and use validation like:
var check = t.struct({
username: t.String,
realname: t.String,
password: t.String,
age: t.Number,
active: t.Boolean
});
module.exports = {
name : 'User',
check: check
};
function create(req, res, next) {
var result = t.validate(req.body, validate.User.check);
var values = new validate.User.check(req.body);
if (!result.isValid()) {
res.send(422, result.errors);
}
}
when I send a post request with params "age=20", then throw an Exception, cannot cat age param type to number
throw new TypeError('[tcomb] ' + message);
^
TypeError: [tcomb] Invalid value "20" supplied to Struct{username: String, realname: String, password: String, age: Number, active: Boolean}/age: Number
207 error Darwin 14.4.0
208 error argv "/usr/local/bin/iojs" "/usr/local/bin/npm" "i" "--save" "tcomb-validation"
209 error node v2.3.3
210 error npm v2.11.3
211 error code EPEERINVALID
212 error peerinvalid The package tcomb does not satisfy its siblings' peerDependencies requirements!
212 error peerinvalid Peer [email protected] wants tcomb@^1.0.0
213 verbose exit [ 1, true ]
Hello
I have found an issue #29 that partially answers my question.
I have a form
var crossValidation = function(p){
var regexp;
switch(p.document_type){
case 2: // Birth certificate
regexp = /^\w+[А-Я]{2}\d{6}$/;
break;
case 4: // Seaman's discharge book
regexp = /^[\d\w]{2}\d{7}$/;
break;
}
return regexp.test(value);
};
var Person = t.subtype(
t.struct({
document_type: t.Number,
document_number: t.String
}),
crossValidation
);
I need to mark as invalid only field "Document number" but not the whole form. Is there a way to implement it?
Is this currently supported?
var Point = t.struct({
x: t.Number,
y: t.Number
});
validate({x: 0, y: 0, z: 0}, Point)
Validation Result will skip z: 0
right now. Which is exactly what i want but without running the validations. given an input and a schema, cast the input based on schema.
tcomb-validations is already doing the casting when validating the input. If we can expose this casting as a function which would just cast and not run the validations.
Something like cast({x: 0, y: 0, z: 0}, Point)
ruturns the { x: 0, y: 0 }
and not run the validations.
hi, when I use restify create an API, and use validation like:
var check = t.struct({
username: t.String,
realname: t.String,
password: t.String,
age: t.Number,
active: t.Boolean
});
module.exports = {
name : 'User',
check: check
};
function create(req, res, next) {
var result = t.validate(req.body, validate.User.check);
if (!result.isValid()) {
res.send(422, result.errors);
} else {
//var values = new validate.User.check(req.body);
var values = result.value;
//var values = new validate.User.check(result.value);
console.log(values);
return User.create(values)
.then(function (entity) {
res.send(entity);
})
.catch(function (err) {
return next(new errors.InternalError(err.message));
});
}
}
when I send a post request with params "age=20", then throw an Exception, and return 422, cannot cat age param type from string to number
[
{
"message": "Invalid value \"30\" supplied to /age: Number",
"actual": "30",
"path": [
"age"
]
},
{
"message": "Invalid value \"1\" supplied to /active: Boolean",
"actual": "1",
"path": [
"active"
]
}
]
For instance, the password you entered passes validation but comes back from the server as incorrect.
So, now you want to manually create a validation exception against this password, perhaps even use the server side error message you got back, and update the UI accordingly. How do you do that?
First, pls look at this:
An error in documentation?!
var Country = t.enums.of('IT, US', 'Country'); // 'IT, US' should be "IT", "US" ???
Second:
import tc from "tcomb";
import {validate} from "tcomb-validation";
// a dictionary of numbers
let Country = tc.enums.of("IT", "US", "Country");
let Warranty = tc.dict(Country, tc.Number);
console.log(validate({US: 2, IT: 1}, Warranty).isValid()); // => false instead of true
Error message:
{ message: 'Invalid value "IT" supplied to /IT: Country',
actual: 'IT',
expected:
{ [Function: Enums]
meta: { kind: 'enums', map: [Object], name: 'Country' },
displayName: 'Country',
is: [Function] },
path: [ 'IT' ] }
Is it possible to validate one field of a struct depending on the value of another? In particular, I have a startDate and an optional endDate field, and I would like to make sure that endDate is after startDate, if endDate is given.
More specifically, if I do something like
var MyType = t.struct({
startDate: t.Dat,
endDate: t.maybe(t.Dat),
foo: t.Num
});
How can I validate MyType
as a whole? It doesn't seem like struct takes a validator function as an argument?
Not sure if the value should change after the validation is done, it should stay undefined?
const t = require("tcomb-validation");
const Point = t.struct({
x: t.maybe(t.Number),
y: t.maybe(t.Number),
}, "Point");
const p1 = Point({ x: 12 });
console.log(p1);
// Struct { x: 12, y: undefined } -- notice y is undefined
console.log(t.validate({ x: 3234 }, Point));
// Struct { errors: [], value: Struct { x: 3234, y: null } } -- notice y here has null value
In code it's strictly set to null? https://github.com/gcanti/tcomb-validation/blob/master/index.js#L132
Hi @gcanti , tcomb validation is really great (helped so much in sharing validation code on different environment & write code in an understandable manner).
One missing feature though is the ability to do async validation.
Any plan to add this feature ?
I'm trying to validate a list of emails with t-comb forms and a custom factory (tags input)
It seems like an empty list does not get passed to the custom validation.
Is there a reason that an empty list is valid ? shouldn't only a maybe
empty list be valid ?
import email from 'email-validation';
import {validate} from 'tcomb-validation';
var t = require('tcomb-form');
let mailForm = t.list(t.subtype(t.Str, (s) => email.valid(s)));
let result = validate([], mailForm);
console.log(result.isValid()); // returns true for empty lists
Please how will I create something like this
t.refinement(t.Number, function(s, locale){ //To do });
Thanks
cc @gcanti
Source:
var values = new validate.User.check(req.body);
let sha256 = crypto.createHash("sha256")
let newPwd = sha256.update(values.password).digest("hex")
console.log(values)
console.log(values.password)
console.log(newPwd)
values.password = newPwd
console.log(values.password)
Console print:
Struct {
username: 'test',
password: '123456',
realname: '测试一下',
age: '25',
role_id: '1',
active: '0' }
123456
8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
123456
I wnat to crypt 'password' field, But Failed
I'm using tcomb-form which of course uses tcomb-validation. I have a custom type which I've defined like this:
minLength(length) {
let type = t.subtype(t.Str, s => s.length >= length, 'minlength');
type.getValidationErrorMessage = function (value, path, context) {
console.log('minLength', context);
if (!type.is(value)) {
var label = context.fields[path].label;
return `${label} must be at least ${length} characters long. '${value}' is too short.`
}
}
return type;
}
The question I'm wondering about is with regards to the context passed into the validate() method. What is the intent of this context object? I noticed in the tcomb-form that it's passing in an object with {path:this.props.ctx.path, context:this.props.context} which seems a bit strange to me because now my form basically looks like this:
<Form options={this.state.options} context={this.state.options} />
Our requirement is to display the label associated w/ the field in the error message, not the path (they often differ in our schema). The reason that feels strange to me is that I'm already giving the form a reference to this.state.options... am I misusing this property?
Hi Giulio!
I wrote a big article / proposition about validation here:
ansman/validate.js#50
I think it should be interesting to you as well because Tcomb-validation has a lot of the same weaknesses as ValidateJs and Joi. No separate parsing step (e.g. no idiomatic way to decipher L10n of input data, no way to "jump through" formats (like splitting tags into array), etc.). You may answer there or here if you wish, I will pick up the discussion anyway.
Hi Giulio!
Currently I am returning i18n keys from getValidationErrorMessage
inside my application. I'd like to return an object instead so that I can use it as props for react-intl
's <FormattedMessage>
component.
For this reason I'd like to change https://github.com/gcanti/tcomb-validation/blob/master/index.js#L9 from t.String
to t.Any
.
By doing this I can avoid my validation code to be coupled to react-intl
or any other future i18n mechanism, and I don't need any special code to handle dynamic locale changes.
Please let me know your opinion on this!
As reported by @chrisui here gcanti/tcomb#220
When DatePicker is displayed the value for it set to current date.
If the value was not changed before submit the value that comes with the form is InvalidDate
In index.js in tcomb-validation there is
validators.enums = function validateIrreducible(x, type, path) {
return {
value: x,
errors: type.is(x) ? [] : [ValidationError.of(x, type, path)]
};
};
type.is(x) returns true in case of InvalidDate that is why no error was detected. Probably there is a need to check something like this:
if (Object.prototype.toString.call(x) === '[object Date]' && isNaN(v.getTime())))
// create an error
or set the value for the date field to current date
Could you please take a look?
Thought you'd like to watch or join: https://github.com/regexps
Hi!
I've got one question: is it possible to strictly validate tcomb structs, so this will fail:
validate(
{ a: 'ok', b: 'invalid extra prop' },
struct({ a: Str })
); // => fail because of extra `b` prop
import the validate function should be simple
var Tcomb = require('tcomb-validation');
var validate = Tcomb.validate;
Hello, I am using tcomb-validation
but cannot figure out how to use it to display custom error messages. I am currently using my own code to validate form models as follows and would appreciate some direction as to how I can switch to using tcomb-validation
which is otherwise great for typechecking.
Here is an example of what I am currently doing:
const string = (msg) => {
return function validate$string (arg) {
let value = _.isFunction(arg) ? arg() : arg;
if (!value || !_.isString(value)) {
return msg || `"${typeof value}" provided instead of "string"`;
}
};
};
const min = (check, msg) => {
msg = msg || `Value should be at least ${check}`;
return function validate$min (arg) {
let value = _.isFunction(arg) ? arg() : arg;
if (_.isFinite(Number(value))) {
if (value < check) return msg;
} else {
if (_.size(value) < check) return msg;
}
};
};
const all = function (...args) {
const predicates = _.flatten(args);
return function validate$all (value) {
for (let pred of predicates) {
let err = pred(value);
if (err) return err;
}
};
};
I can use the above functions as follows to display custom error messages:
// Generate a validator and provide custom error messages
const validatePassword = all([
string('Password must be provided'),
min(10, 'Short passwords are not allowed; please use at least 10 characters')
]);
validatePassword('1234567') //=> 'Short passwords are not allowed...'
Basically validatePassword returns the first error message that it encounters.
Another example:
const validateStartDate = dateIsAfter(Date.now(), 'Date must be in the future');
// Validator gets the value lazily
const validateEndDate = dateIsAfter(() => startDate, 'Date must be after start date');
Is something like this possible with tcomb-validation
? I couldn't figure it out from the documentation so far.
Somewhat related, how do tcomb-validation and tcomb-form keep types from being disable in production mode? I though that the type checking gets disable when NODE_ENV === 'production'. I like that but in case of forms, one would want to keep the checking enabled.
Thanks for your help and a providing a great tool.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.