Conditional validation with joi - joi

I try to implement conditional validation with Joi. I have an endpoint that can accept either:
{
from: 'abc'
}
or
{
type: 'some-type'
}
If the type field isn't present, the from field is mandatory and if the from field isn't present, the type field is mandatory. The type can only accept a set of value.
I tried the following approach without success:
type: joi.alternatives().conditional('from', { is: joi.string().empty(), then: joi.string().required().valid('val1', 'val2'), otherwise: joi.optional() })
.messages({
'any.valid': 'type.not.supported.value',
'any.required': 'type.required'
}),
from: joi.alternatives().conditional('type', { is: joi.string().empty(), then: joi.required(), otherwise: joi.optional() })
.messages({
'any.valid': 'from.not.supported.value',
'any.required': 'from.required'
})
Thanks for your help!
Thierry

What you describe sounds like an or constraint.
...a relationship between keys where one of the peers is required (and more than one is allowed)
The following schema would work:
joi.object().keys({
type: joi.string().valid('val1', 'val2'),
from: joi.string()
}).or('type', 'from');

Related

Meteor Mongo Collection is not using typescript interface

I want to describe Mongo.Collection schema via typescript interface to have a strict check for type on fetch, forEach, etc.
interface IChat {
_id: string;
name?: string
};
const Chats = new Mongo.Collection<IChat>('chat');
// (method) Mongo.Cursor<IChat>.forEach(callback: <T>(doc: T, index: number, cursor: Mongo.Cursor<T>) => void, thisArg?: any): void
Chats.find().forEach(c => {
console.log(c._id); // Property '_id' does not exist on type 'T'.
// Why "type T" if it should be the type IChat???
});
But facing next error: Property '_id' does not exist on type 'T'.
What I'm doing wrong?
Link to typescript playground
Error
Collection.find().forEach() definition
In this case you're missing the .fetch() call after .find().
It may be that mathamagically Meteor+Mongo can handle the syntax as you have it now but the easy way to make Typescript happy here is adjusting it to be the following:
interface IChat {
_id: string;
name?: string
};
const Chats = new Mongo.Collection<IChat>('chat');
Chats.find().fetch().forEach(c => {
console.log(c._id);
});
link to the solution in Typescript Playground

One way association with array of reference _id in sails js

I'm trying to use one way association because I need only to have reference from 1 model to other model but not vice versa.
Model Arts:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
model: 'Softwares'
}
}
}
Model Softwares:
module.exports = {
attributes: {
name: {type: 'string', required: true}
}
}
This is my api:
http://localhost:1337/api/v1/arts/create
if this is my request body, it works fine:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": "5e70309cbf12b61299d6c528",
}
but i want to store array of softwareUsed, so i tried:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": ["5e70309cbf12b61299d6c528", "5e70309cbf12b61299d6c529"],
}
but i got an error with that:
error: OperationalError [UsageError]: Invalid new record.
Details:
Could not use specified `softwareUsed`. Expecting an id representing the associated record, or `null` to indicate there will be no associated record. But the specified value is not a valid `softwareUsed`. Instead of a string (the expected pk type), the provided value is: [ '5e70309cbf12b61299d6c528', '5e70309cbf12b61299d6c529' ]
I also tried to make it array in model:
softwareUsed: [{
model: 'Softwares'
}]
but still don't work.
Is there a way to that in one way association or I need to use other association, but how can I achieve that?
Thank you.
I think you need to label the softwareUsed attribute with a collection, not a model:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
collection: 'Softwares'
}
}
}
All the documentation on one-to-many in the sails docs involves two-way associations and adding a via attribute, but I think this way works for a one-way association.
Of course, your first api call may now longer work: you may need to wrap the single software id in an array.

Do Typescript Interfaces allow you to discard attributes not defined on the interface?

I have a function that performs a search. The search can be done a few different ways (by looking for an ID or by querying a few attributes). However, I want to limit what attributes can be passed in. I thought I could do something like:
interface Search {
_id?: string
people?: number
partyName?: string
otherField? string
}
function search(query: Search) {
myDbConnection.find(query).then(... // etc
}
The problem is that any object will conform to this, and query can contain extra attributes. For example, this could be passed:
search({otherField: "foo", aProtectedField: "bar"})
and aProtectedField would be passed along to find.
I am wondering if there is a typescript way of enforcing the attributes passed. Sort of strong-parameters from the Rails world. I know I can do things like pick form lodash or maybe even make a SearchObject class and use the constructor as a means of discarding the extra attributes, but I feel like there is a way to do this within Typescript that I just don't know about.
You could make all the properties required then do an assertion to pass in a subset of the properties.
For example:
interface Search {
_id: string;
people: number;
partyName: string;
otherField: string;
}
function search(query: Search) {
// code here
}
search({ people: 2 } as Search); // ok
search({ otherField: "foo", aProtectedField: "bar" }); // error, good
search({ otherField: "foo", aProtectedField: "bar" } as Search); // error, good
What version of Typescript are you on? Since Typescript 1.6 there has been improved checking for object literals.
On TS 1.8 when I try to run your code I get:
error TS2345: Argument of type '{ otherField: string; aProtectedField: string; }' is not assignable to parameter of type 'Search'.
Object literal may only specify known properties, and 'aProtectedField' does not exist in type 'Search'.
This and noImplicitAny should catch the errors you're worried about.
I want to limit what attributes can be passed in. [...] query can contain extra attributes.
The way I see it, this is controversial. If you limit what attributes a given object might contain, then it is, by definition, limited to that set of attributes, and cannot contain others not allowed by its specs.
Since there is practically nothing you can do about the any type, my recommendation is to resolve this the type-safe way, by defining an option for additional attributes:
interface Search {
_id?: string;
people?: number;
partyName?: string;
additionalFields?: { [key: string]: any };
}
search({ people: 2 }); // ok
search({ _id: "jd", people: 2 }); // ok
search({ _id: "jd", additionalFields: { otherField: "foo" } }); // ok

Updating an array field in a mongodb collection

I am trying to update my collection which has an array field(initially blank) and for this I am trying this code
Industry.update({_id:industryId},
{$push:{categories: id:categoryId,
label:newCategory,
value:newCategory }}}});
No error is shown, but in my collection just empty documents({}) are created.
Note: I have both categoryId and newCategory, so no issues with that.
Thanks in advance.
This is the schema:
Industry = new Meteor.Collection("industry");
Industry.attachSchema(new SimpleSchema({
label:{
type:String
},
value:{
type:String
},
categories:{
type: [Object]
}
}));
I am not sure but maybe the error is occuring because you are not validating 'categories' in your schema. Try adding a 'blackbox:true' to your 'categories' so that it accepts any types of objects.
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [Object],
blackbox:true // allows all objects
}
}));
Once you've done that try adding values to it like this
var newObject = {
id: categoryId,
label: newCategory,
value: newCategory
}
Industry.update({
_id: industryId
}, {
$push: {
categories: newObject //newObject can be anything
}
});
This would allow you to add any kind of object into the categories field.
But you mentioned in a comment that categories is also another collection.
If you already have a SimpleSchema for categories then you could validate the categories field to only accept objects that match with the SimpleSchema for categories like this
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [categoriesSchema] // replace categoriesSchema by name of SimpleSchema for categories
}
}));
In this case only objects that match categoriesSchema will be allowed into categories field. Any other type would be filtered out. Also you wouldnt get any error on console for trying to insert other types.(which is what i think is happening when you try to insert now as no validation is specified)
EDIT : EXPLANATION OF ANSWER
In a SimpleSchema when you define an array of objects you have to validate it,ie, you have to tell it what objects it can accept and what it can't.
For example when you define it like
...
categories: {
type: [categoriesSchema] // Correct
}
it means that objects that are similar in structure to those in another SimpleSchema named categoriesSchema only can be inserted into it. According to your example any object you try to insert should be of this format
{
id: categoryId,
label: newCategory,
value: newCategory
}
Any object that isn't of this format will be rejected while insert. Thats why all objects you tried to insert where rejected when you tried initially with your schema structured like this
...
categories: {
type: [Object] // Not correct as there is no SimpleSchema named 'Object' to match with
}
Blackbox:true
Now, lets say you don't what your object to be filtered and want all objects to be inserted without validation.
Thats where setting "blackbox:true" comes in. If you define a field like this
...
categories: {
type: [Object], // Correct
blackbox:true
}
it means that categories can be any object and need not be validated with respect to some other SimpleSchema. So whatever you try to insert gets accepted.
If you run this query in mongo shell, it will produce a log like matched:1, updated:0. Please check what you will get . if matched is 0, it means that your input query is not having any matching documents.

Sails' Waterline email validation always failed

I was working on my application and all was ok, but after reinstalling system to Windows 10 i always have an email validation fail. I don't really think this happens because of Windows 10, but it's only thing that was changed.
For testing i have created new shiny model:
module.exports = {
attributes: {
email: {
type: 'string',
required: true,
unique: true,
email: true
}
}
}
And if i start application with sails console and type TestModel.create({ email: 'alex#yahoo.com' }).exec(function(err, created) { console.log(err); console.log(created); }) i'll get something like this:
Error (E_VALIDATION) :: 1 attribute is invalid
at WLValidationError.WLError (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLError.js:26:15)
at new WLValidationError (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLValidationError.js:20:28)
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\query\validate.js:46:43
at allValidationsChecked (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:210:5)
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:49:16
at done (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:239:19)
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:40:16
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:201:14
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:49:16
at done (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:239:19)
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:40:16
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:164:64
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:162:20
at C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:230:13
at _arrayEach (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:81:9)
at _each (C:\Users\My name is Alex\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\node_modules\async\lib\async.js:72:13)
Invalid attributes sent to TestModel:
• email
• "email" validation rule failed for input: 'alex#yahoo.com'
If i disable email validation, all works nice.
{ email: 'alex#yahoo.com',
createdAt: '2015-08-08T21:09:25.118Z',
updatedAt: '2015-08-08T21:09:25.118Z',
id: 1 }
Own email validation method - isn't good solution.
I tried trivial stupid things like database reinstall, but it doesn't helps. Sorry for my bad english, hope i'll find answer here.
I have one weird solution now. I changed attribute type to 'email' and deleted email: true type.
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
}
}
};
And now it works. It's ok for me, but it is doesn't documented and i still want to know why default way doesn't works.
According to waterline docs:
Validations are defined directly in you Collection attributes. In addition you may set the attribute type to any supported Anchor type and Waterline will build a validation and set the schema type as a string for that attribute.
So when you set a type that is not one of the core types waterline will use it to validate the type in one of the validation rules supported by Anchor and keep the data as string
Therefore defining the schema like this will work:
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
}
}
};