A user will be sending contact information as an array of objects like the following, with the phone number being optional and the email being required:
{
"contact": [{
"type": "phone",
"value": "555-555-5555"
}, {
"type": "email",
"value": "test#test.com"
}]
}
I would like to ensure there is an email object inside the array. I tried a Joi validation like this:
contact: Joi.array().items(Joi.object().keys({
type: Joi.string().valid('phone', 'email'),
value: Joi.string()
.when('contact.type', { is: 'phone', then: Joi.string() })
.when('contact.type', { is: 'email', then: Joi.string().email().required() })
}))
.when('contact.type', { is: 'phone', then: Joi.array().min(2).required() }),
But I get the following error:
Error: Item cannot come after itself: contact
It seems it doesn't like me giving it a length in this manner but I can't figure out any other way to do it. Any help would be appreciated. Thanks.
This schema combining .when, .has, and .unique, should work:
Joi.object({
contact: Joi.array().items(
Joi.object().keys({
type: Joi.string().valid('phone', 'email').required(),
value: Joi.string().when('type', { is: 'email', then: Joi.required() })
}).required(),
)
.has(Joi.object({ type: 'email', value: Joi.exist() }))
.unique('type').min(1).max(2)
})
Let's see the rules:
the object with the type 'email' must exist;
That's why I've added
.has(Joi.object({ type: 'email', value: Joi.exist() }))
This means that the array must have at least one of these elements.
We don't want duplicates, right?
.unique('type').min(1).max(2)
The array will have either 1 element, or 2, with different types.
When the type is 'email', the value should be required, and optional otherwise. That's what we are saying here:
value: Joi.string().when('type', { is: 'email', then: Joi.required() })
Thank you #soltex for your answer.
First I'm not sure we should exclude duplicates. Some people have multiple phone numbers.
Second, your answer did not quite work. Here is my updated answer based on what you wrote:
contact: Joi.array().items(Joi.object().keys({
type: Joi.string().valid('phone', 'email').required(),
value: Joi.string().required(),
}))
.has(Joi.object({ type: 'email', value: Joi.string().email() }))
Once I include the .has method then there is no reason to include the .when method. I also want the email to be a valid email not just that it exists. I changed value to required because if the user sends type phone I still want them to include the value.
Once again thanks for your guidance.
Related
I'm using AJV to validate a HTTP request payload against a schema. However, I see an error reported that I was not expecting. This is a code example to demonstrate the issue:
const schema = {
type: 'array',
minItems: 1,
items: {
anyOf: [
{
type: 'object',
properties: {
op: {
enum: ['replace']
},
path: {
type: 'string',
pattern: '/data/to/foo/bar',
},
value: {
type: 'string',
},
},
},{
type: 'object',
properties: {
op: {
enum: ['replace']
},
path: {
type: 'string',
pattern: '/data/to/baz',
},
value: {
type: 'object',
required: ['foo', 'bar'],
properties: {
foo: {
type: 'string',
},
bar: {
type: 'string',
},
}
}
}
}
],
},
}
const validator = new ajv()
const compiledValidator = validator.compile(schema)
const data = [
{ // this object should pass
op: 'replace',
path: '/data/to/foo/bar',
value: 'foo',
},
{ // this object should fail in the `value` mismatch (missing required attribute)
op: 'replace',
path: '/data/to/baz',
value: {
foo: 'bar',
},
},
]
compiledValidator(data)
console.log(compiledValidator.errors)
The schema defines a number of objects to which an incoming list of data objects should match. The first data item matches the schema (first item schema), however the second data item misses a required attribute (bar) in the value object.
When I run the above code I get the following output:
[
{
instancePath: '/1/path',
schemaPath: '#/items/anyOf/0/properties/path/pattern',
keyword: 'pattern',
params: { pattern: '/data/to/foo/bar' },
message: 'must match pattern "/data/to/foo/bar"'
},
{
instancePath: '/1/value',
schemaPath: '#/items/anyOf/1/properties/value/required',
keyword: 'required',
params: { missingProperty: 'bar' },
message: "must have required property 'bar'"
},
{
instancePath: '/1',
schemaPath: '#/items/anyOf',
keyword: 'anyOf',
params: {},
message: 'must match a schema in anyOf'
}
]
I understand the 2nd and the 3rd (last) errors. However, The first error seems to indicate that the path doesn't match path requirements of the first item schema. It is true that the 2nd data item doesn't match the 1st schema item but I don't seem to understand how it is relevant. I would assume that the error would be focused around the value, not the path since it matches on the path schemas.
Is there a way to get the error reporting more focused around the errors that matter?
There is no way for the evaluator to know whether you intended the first "anyOf" subschema to match or the second, so the most useful thing to do is to show you all the errors.
It can be confusing because you don't need to resolve all the errors, just some of them, which is why some implementations also offer a heirarchical error format to make it more easy to see relationships like this. Maybe if you request that ajv implement more of these error formats, it will happen :)
You can see that all of the errors pertain to the second item in the data by looking at the instancePath for each error, which all start with /1. This is the location within the data that generated the error.
So let's look at the second item and compare it against the schema.
{ // this object should fail in the `value` mismatch (missing required attribute)
op: 'replace',
path: '/data/to/baz',
value: {
foo: 'bar',
},
}
The schema says that an item should either (from the anyOf) have
path: '/data/to/foo/bar' and value: { type: 'string' }, or
path: '/data/to/baz' and value: { required: [ 'foo', 'bar' ] }
The reported errors are:
The first case fails because path is wrong.
The second case fails because /value/bar is not present.
The last error is just the anyOf reporting that none of the options passed.
I recently migrated to new joi repo (#hapi/joi => joi)
Now I am getting error when running server
throw new AssertError([result.error.details[0].message]);
^
Error: "language" is not allowed
I searched google and SO , but cant find solution
This is my code :
forgetUser: {
query: {
email: Joi.string().regex(/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)
.required()
.options({ language: { string: { regex: { base: 'must be valid' } } } })
.label('Email')
}
},
Please help me?
This is caused when you are passing an unknown field to a Joi schema.
for example, you have this Joi schema:
Joi.object({
name: Joi.string().required(),
email: Joi.string().required(),
});
and you pass this object to validate:
{
name: "John Doe",
email: "johndoe#gmail.com",
language: "en"
}
The validation will throw an error / failed because language is not allowed inside the schema.
To prevent this, you can pass stripUnknown options on the Joi Schema so it will strip unknown fields
Joi.object({
name: Joi.string().required(),
email: Joi.string().required(),
}).options({ stripUnknown: true });
or you can pass allowUnknown so it will ignore unknown fields
Joi.object({
name: Joi.string().required(),
email: Joi.string().required(),
}).options({ allowUnknown: true });
You can read more about validation options here
It is not clear from the question what you are trying to achieve (maybe add more details?).
If you are trying to validate email, there is already a built-in function to do so: string.email().
If you still want to do a custom regex matching, there is an built-in function for this too: string.pattern().
If you want to replace the built-in error messages to custom error messages, Joi provides that by using .messages(). Check the answer here: https://stackoverflow.com/a/58234246/1499476.
Basically, you can do something like:
forgetUser: {
query: {
email: Joi.string().pattern(/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)
.required()
.label('Email')
.messages({
'string.pattern.base': '{#label} must be valid', // --> Email must be valid.
'string.base': '{#label} should be a type of "text"',
... // other customisations
})
}
},
string.pattern() can also be used as alias string.regex() (as you are already doing).
In sequelize i have a model:
{
modelId: {
type: DataTypes.UUID ,
allowNull: false,
primaryKey: true,
defaultValue: DataTypes.UUIDV4
}
name: DataTypes.STRING(1024),
type: DataTypes.STRING,
obj: DataTypes.JSON
}
and in DB, my obj is an array like this:
[{
id: '123456',
text: 'test',
name 'xpto'
},{
id: '32554',
text: 'test2',
name 'xpte'
},{
id: '36201',
text: 'test3',
name 'xpta'
}]
i tried these:
btp.findAll({
where: {
obj:{
[Op.contains]:[{id: req.body.id}]
}
},
attributes: ['modelId','name','type','obj']
})
but does not work, return this error:
{"name": "SequelizeDatabaseError",
"parent": {
"name": "error",
"length": 128,
"severity": "ERROR",
"code": "42704",
"file": "parse_coerce.c",
"line": "1832",
"routine": "enforce_generic_type_consistency",
"sql":"....."}
so, i need to find in database all entries have in obj, id: '123456'
my question is the same than this:
https://github.com/sequelize/sequelize/issues/7349
but thats does not working for me, i need to return all entries that contains...
i'm using "sequelize": "4.28.6", and "pg-hstore": "^2.3.2",
can any one help?
I'm not familiar with the specific error you're getting, but one potential issue is that you're using a JSON column instead of JSONB. In Postgres, JSON columns just store the raw JSON text, and so don't support the containment operator (#>) which is needed for Sequelize's "contains".
All you need to do to fix this is change the column definition in the model to:
obj: DataTypes.JSONB
The only other issue I can think of would be req.body.id having an invalid value. I'd suggest verifying that it's actually getting a valid ID string.
Beyond these 2 potential issues, the query you wrote should work.
I have following validation schema, and sample data.
var schema = Joi.alternatives().try(
Joi.object().keys({
searchTerm: Joi.string().trim().min(3).label('Search Term').options({ language: { any: { empty: 'should not be empty' } } }),
location: Joi.string().allow(''),
searchType: Joi.string().valid('people')
}),
Joi.object().keys({
searchTerm: Joi.string().allow(''),
location: Joi.string().trim().min(3).label('Location').options({ language: { any: { empty: 'should not be empty' } } }),
searchType: Joi.string().valid('people')
})
);
Sample data is:
{searchTerm: "", searchType: "people", location: ""}
Should not pass and show a message Please enter either search term or location. Make sure it contains 3 characters at least
{searchTerm: "as", searchType: "people", location: ""}
Should not pass and show a message Search term must contain 3 characters at least
{searchTerm: "test", searchType: "people", location: ""} // Should pass
My validation schema shows both message in failure situations
You can simplify your joi schema to this
const schema = Joi.object().keys({
searchTerm: Joi.string().trim().min(3),
location: Joi.string().allow(''),
searchType: Joi.string().valid('people'),
}).or('searchTerm', 'location').error(new Error('Please enter either search term or location. Make sure it contains 3 characters at least'));
However this will not meet your second condition(Search term must contain 3 characters at least) as there can be only one error message.
I am following this tutorial to create a form in my project, which is leveraging the Apostrophe CMS. When i follow the tutorial, I am able to create the form and submit, and understand how to view the form submission in the Admin Console.
However, when I begin to customize the form to fit my project-specific needs, I break the form, causing the form validation to fail. The error i get upon submitting the form is:
TypeError: Cannot read property 'length' of undefined
at Object.convertString [as convert] (user.js:727)
at user.js:145
at async.js:181
at iterate (async.js:262)
at Object.async.forEachOfSeries.async.eachOfSeries (async.js:281)
at Object.async.forEachSeries.async.eachSeries (async.js:214)
at Object.self.convert (user.js:127)
at convert (always.js:57)
at async.js:718
at iterate (async.js:262)
My changes, other then adjusting the field definition object of my contact-form:index.js file, are mostly in the contact-form-widgets:widget.html file.
In the tutorial, the contact-form-widgets:widget.html view imports apostrophe-schemas:macros.html, and uses html files from the apostrophe-schemas and apostrophe-ui modules to build the html of the form. My specific questions therefore have to do with the importance of those two modules. Are those modules simply used for the front end display of the form? Do the contents of the views of those modules have any bearing on the submission and post-submissions tasks of the form? If the answer is negative, this means I am not configuring the form correctly, so therefore, are there any resources to help solve that problem?
Here is my contact-form:index.js config file:
var async = require('async');
module.exports = {
extend: 'apostrophe-pieces',
name: 'contact-form',
label: 'Contact Form',
alias: 'contactForm',
addFields: [
{
name: 'name',
type: 'string',
label: 'First & Last Name',
required: true
},
{
name: 'company',
type: 'string',
label: 'Company Name',
required: true
},
{
name: 'email',
type: 'string',
label: 'Email Address',
required: true
},
{
name: 'phone',
type: 'string',
label: 'Phone Number & Extension',
required: true
},
{
name: 'subject',
type: 'string',
label: 'Subject',
required: true
},
{
name: 'message',
type: 'string',
label: 'Message',
textarea: true,
placeholder: "Don't be stupid"
}
],
permissionsFields: false,
afterConstruct: function(self) {
self.setSubmitSchema();
},
construct: function(self, options) {
self.setSubmitSchema = function() {
self.submitSchema = self.apos.schemas.subset(self.schema,
[ 'name', 'company', 'email', 'phone', 'subject', 'message' ]
);
};
self.submit = function(req, callback) {
var piece = {};
return async.series([
convert,
insert
], callback);
function convert(callback) {
return self.apos.schemas.convert(req, self.schema, 'form', req.body, piece, callback);
}
function insert(callback) {
return self.insert(req, piece, { permissions: false }, callback);
}
};
}
};
I'm the lead developer of Apostrophe at P'unk Avenue. We had some conversation in another forum but just for the record, there turned out to be two issues:
The form was submitting on any button click because that is what button elements do when they are of type "submit," and "submit" is the default value for type (at least in some browsers). It's not an Apostrophe issue and can be resolved by setting the type attribute as desired.
The form markup was custom and did not have wrapper elements such as data-name="title", etc. (corresponding to the schema) around the actual form field elements. You may use custom markup but all of the "moving parts" found in our official schemaMacros.html need to be there, i.e. the data attributes need to exist.
We do plan to make it easier to use custom markup without those wrappers, at least in cases where we can figure out which fields are which without them.
FYI, an element of type="file" will not work out of the box, but check out the attachment field type in apostrophe's schemas, which is pretty amazing for that role.