I have an array of objects which contain the same values including locale.
Locale is a language: en, de
I have this schema:
descriptions: yup.object().shape({
data: yup.array().of(
yup.object().shape({
name: yup.string().required('required'),
locale: yup.string().oneOf(['de', 'en']),
})
),
})
I want to get errors returned like this:
{ descriptions: { data: [{ name_en:"required"}, ...] } }
I tried to do it with .label() but, it returns the same key or I'm doing it wrong.
Also I tried with transformKeys like this:
.transformKeys(key => `${key}_something`)
Even if this works, I still need access to the locale value
Related
I am using yup for object validations. I have the following schema
const schema = yup.object().shape({
name: yup.string().required(),
});
I am validating it with object
{
"name": "Foo",
"desc": "Lorem ipsum"
}
Yup validates this object although it has an extra key desc. I want to raise the error for extra keys in the object.
I have tried with abortEarly and stripUnknown in .validate but it doesn't work.
schema.validateSync(data, { strict: true, stripUnknown: true })
You need to append the .strict() to the object you are validating. This makes the validation fail and then you can handle the error however you wish to do that.
So, in your case, change your schema to this:
const schema = yup.object().shape({
name: yup.string().required()
}).noUnknown(true).strict();
await schema.validate(data, { abortEarly: false });
I have object types that have a 'translations' property where the fields that can be translated into different languages are passed into the specific 'lang' property as you can see in the schema below.
An English translation is always required and the rest of the languages are optional, I can achieve this by setting .default(undefined) to the optional languages.
When a language is present and the validation for its inner fields fails, the error is always associated to the field itself ('name' in this case). This behaviour is expected.
What I else want to achieve and I don't know how is to show an error when the 'translations' property 'en' is not present with a message like 'An English translation is required'.
const categoryTranslationsSchema = object({
name: string()
.min(3, 'Must have at least 3 characters.')
.max(16, 'Cannot be longer than 16 characteres.')
.required('Must provide a name.')
})
const categorySchema = object({
slug: string()
.min(3, 'Must have at least 3 characters.')
.max(16, 'Cannot be longer than 16 characteres.')
.lowercase()
.trim()
.matches(/^(?![0-9-]+$)(?:[a-z]{2,}-?|[0-9]-?)+(?<!-)$/gm, 'Must start with a letter and can'
+ ' only contain letters, numbers or dashes (no more than one consecutive).')
.required('The slug is required.'),
translations: object({
en: categoryTranslationsSchema,
es: categoryTranslationsSchema
.default(undefined),
de: categoryTranslationsSchema
.default(undefined)
})
})
I think you should look into using a custom locale dictionary. Which allows you to customize the default messages used by Yup, when no message is provided with a validation test. If any message is missing in the custom dictionary the error message will default to Yup's one. It also provided enables multi-language support.
https://github.com/jquense/yup#using-a-custom-locale-dictionary
import { setLocale } from 'yup';
setLocale({
mixed: {
default: 'Não é válido',
},
number: {
min: 'Deve ser maior que ${min}',
},
});
// now use Yup schemas AFTER you defined your custom dictionary
let schema = yup.object().shape({
name: yup.string(),
age: yup.number().min(18),
});
schema.validate({ name: 'jimmy', age: 11 }).catch(function (err) {
err.name; // => 'ValidationError'
err.errors; // => ['Deve ser maior que 18']
});
If you can't get what you're looking for with just that try combining it with yup.lazy which creates a schema that is evaluated at validation/cast time. This can be nested inside your object schema as well. https://github.com/jquense/yup#yuplazyvalue-any--schema-lazy
here is an idea of what you can do:
translations: Yup.lazy(value => {
switch (typeof value) {
case 'object':
return Yup.object(); // schema for object
case 'string':
return Yup.string().min(MIN_DESC_LENGTH).max(_MAX_NAME_LENGTH); // schema for string
default:
return Yup.mixed(); // here you can decide what is the default
}
})
Well, the problem when validating nested object is that they default to empty objects {}, for that reason it passes the validation and enters the inner validation.
The solution is then making all objects .default(undefined) that way we can add more requirements to the object validation itself, in this case making it required.
So the solution was as simple as that:
const categorySchema = object({
slug: string()
.min(3, 'Must have at least 3 characters.')
.max(16, 'Cannot be longer than 16 characteres.')
.lowercase()
.trim()
.matches(/^(?![0-9-]+$)(?:[a-z]{2,}-?|[0-9]-?)+(?<!-)$/gm, 'Must start with a letter and can'
+ ' only contain letters, numbers or dashes (no more than one consecutive).')
.required('The slug is required.'),
translations: object({
en: categoryTranslationsSchema
.default(undefined)
.required('An English translation must be provided.'),
zh: categoryTranslationsSchema
.default(undefined)
}).default(undefined)
.required('Translations must be defined.'),
})
How can I group my queries into namespaces in GraphQL? I have something like this right now:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: fields,
});
and in fields I have field -> object mappings and it works fine, but I'd like to group these mappings into two groups (live and historical). If I modify the above code to this however:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: {
historical: {
type: new g.GraphQLObjectType({
name: "historical",
fields: fields,
})
}
},
});
everything resolves to null. How can I write a resolver for this grouping? Is it possible at all?
so often people want namespaces for the sake of splitting up code, not sure if this is your end goal but you could achieve that this way aswell:
# in one file
type Mutation {
login(username: String, password: String): User
}
# in other file
extend type Mutation {
postX(title: String, message: String): X
}
I'm on JOI 14 and can't seem to find upgrade guides to move towards 17. I see people posting similar questions for JOI 16, but the last update was 3 months ago. It doesn't look like type was required back in 16 based on what I see in How to add custom validator function in Joi?.
I am looking at https://joi.dev/api/?v=17.3.0#extensions and the description of type is The type of schema. Can be a string, or a regular expression that matches multiple types..
I tried something like this:
const snakeAlpha = joi => {
return {
type: 'object',
name: 'snakeAlpha',
base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)
};
};
const customJoi = Joi.extend({
type: 'object',
rules: {
snakeAlpha
}
});
It gives me this error:
ValidationError: {
"type": "object",
"rules": {
"snakeAlpha" [1]: "[joi => {\n return {\n type: 'object',\n name: 'snakeAlpha',\n base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)\n };\n}]"
}
}
[1] "rules.snakeAlpha" must be of type object
I am confused since said object. I also tried string since that's what the base is, but it had same error message.
Update
I also realize the original example only covered one simple rule that isn't referencing joi (regex). I also have validators that reference other custom ones lke the below. Bonus points to solve this case too.
const arrayKebabAlpha = joi => {
return {
type: 'string',
name: 'arrayKebabAlpha',
base: joi.array().items(joi.kebabAlpha())
};
};
The documentation for Joi extensions is disappointingly lacklustre for such a useful feature. Fortunately a lot of Joi's core is written using extensions so a lot can be learned from looking at the source.
If I were to write your rule as an extension it'd be like this:
const customJoi = Joi.extend(joi => ({
type: 'string',
base: joi.string(),
messages: {
'string.snakeAlpha': '{{#label}} must be snake case'
},
rules: {
snakeAlpha: {
validate(value, helpers)
{
if (!/^[a-z]+(_[a-z]+)*$/.test(value))
{
return helpers.error('string.snakeAlpha', { value });
}
return value;
}
}
}
}));
Which can be used like:
customJoi.object().keys({
foo: customJoi.string().snakeAlpha()
});
UPDATE
Whether this is the correct way of working with dependant extensions, I'm not sure, but this is how I typically handle them...
I first define my extensions in an array ensuring dependant extensions are defined first. Then I'll iterate through the array re-using the previous customJoi instance so the next extension includes those defined before it. A simple working example will probably explain better than I can put into words!
(I've also simplified the extensions to be more inline with how you're used to using them)
const Joi = require('joi');
let customJoi = Joi;
const extensions = [
joi => ({
type: 'snakeAlpha',
base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)
}),
// this instance of 'joi' will include 'snakeAlpha'
joi => ({
type: 'kebabAlpha',
base: joi.string().regex(/^[a-z]+(-[a-z]+)*$/)
}),
// this instance of 'joi' will include 'snakeAlpha' and 'kebabAlpha'
joi => ({
type: 'arrayKebabAlpha',
base: joi.array().items(joi.kebabAlpha())
})
];
extensions.forEach(extension =>
customJoi = customJoi.extend(extension));
customJoi.assert([ 'hello-world' ], customJoi.arrayKebabAlpha());
In Mongoose, I have two collections, with one referencing the other. Is it possible to have a find query that selects records based on a value in the other. An example of what I am try to get at (not actual schemas):
const CarModelSchema = new mongoose.Schema({
name: String,
brand: { type: mongoose.Schema.Types.ObjectId, ref: 'CarBrand' }
});
const CarBrandSchema = new mongoose.Schema({
name: String,
country: String
});
I then want to perform a query of the form, without needing to do two queries:
CarModelSchema.find({ 'brand.country': 'GER' });
So far I haven't been able to make this work, so I am wondering whether this can be done in Mongo or whether I am approaching it wrong?
Yes it is possible.
I realize you don't have models for your schemas so add them like this:
const CarModel = mongoose.model('CarModel', CarModelSchema);
const CarBrand = mongoose.model('CarBrand', CarBrandSchema);
Also brands should be defined like this:
brand: [{ type: mongoose.Schema.Types.ObjectId, ref: 'CarBrand' }] //added the brackets
You can then run a find query to filter by country by doing the following:
CarModel.
find(...).
populate({
path: 'brand',
match: { country: { $eq: 'GER' }},
// You can even select the field you want using select like below,
select: 'name -_id',
//Even limit the amount of documents returned in the array
options: { limit: 5 }
}).
exec();
And that should do it, as long as the ObjectIds saved in brands array in the CarModel collection are valid or exist.
Using match in your population will do the work.
CarModel.find()
.populate({
path: 'brand',
model: CarBrandModel,
match: { country: { $eq: 'GER' }},
})
.exec()
Keep in mind you have to define CarModel and CarBrandModel like this:
const CarModel = mongoose.model('CarModel', CarModelSchema)
const CarBrandModel = mongoose.model('CarBrandModel', CarBrandSchema)
Yes, you are doing it wrong.
In CarModelSchema.brand there is not string saved, there is ObjectId saved, therefore you have to find that ObjectId (the reference).
You can do it manually - first finding the CarBrandSchema.find({ 'country': 'GER' }); and then use its ObjectId (=_id), or you can use https://mongoosejs.com/docs/populate.html to populate your CarModel with the CarBrand object.