Ajv library: Accessing keys in the nested schema validation javascript - javascript

I'm working on the use case where I need to validate the schema using ajv lib of the provided data which can be nested.
Now the problem is, Schema could change based on the value of a particular variable, which is not in the scope where this check is to be done.
how do I achieve it through ajv.
I tried using if-else & data & const but no luck.

I've recently encountered similar use case with ajv validator.
By looking at your issue I believe you need to access some other scope of the actual object passed for validation, In my case it was lying in the root of the object.
So I used the validate function in ajv's User Defined Keyword section, which in turn is giving me the whole object from top level in the first index of arguments itself, that way I accessed my dependent key in validate function and used it in my ajv IF: condition, eg
ajv.addKeyword({
keyword: "isRegular",
validate: (...test) => {
const test1 = test[1]
return test1.dependentKeyFromRootOfObject === "REGULAR"
},
})
and used the then created keyword isRegular in the nested object's IF: condition like
if: { isRegular: true },
then: {
properties: {
rcType: { type: "string" },
date: { type: "string" },
},
required: ["rcType", "date"],
additionalProperties: false,
},
else: {
properties: {
rcType: { type: "string" },
},
required: ["rcType"],
additionalProperties: false,
},
Hope this helps.

Related

Prisma/React Query Dependent undefined type challenges

I would like to take the output of one query (a TRPC query on Prisma) and use this as the dependent input in a future query.
I followed the dependent documentation for React Query but running into type errors that the return of the first may possibly be undefined (e.g. product is possibly 'undefined'):
const { data: product } = api.product.getUnique.useQuery({ id: pid });
const options = api.option.getAll.useQuery(
{
product: product.productSize,
region: product.productRegion,
},
{ enabled: !!product }
);
Does the inclusion of enabled not already handle this? If not, what is the correct way to adapt for Typescript.
Just casting the product value as a boolean return any truthy value (f.e if product will be equal to {} it will still result in true, that means that product won't necessarily have the productSize or productRegion properties, I would change it first to:
{ enabled: !!product && product.productSize && product.productRegion }
If that doesn't fix the typescript error, you as a developer can know for sure that the values are actually there so what you can use the as keyword in typescript to tell it that you know for sure that the type is what you want it to be:
(In this example I assumed that the values are string but you can change it to number or whatever the true value of them are)
const options = api.option.getAll.useQuery(
{
product: product.productSize as string,
region: product.productRegion as string,
},
{ enabled: !!product && product.productSize && product.productRegion }
);

In DymanoDB/Dynamoose, how can I set a schema of a map of string keys and number values?

So I have this instructions:
Schema Defs:
Result object:
It's a map of string keys and number values
"result": { "M": { [STRING]: { "N": "401" } },
This is what I have so far
result: {
type: Object,
schema: {
// I am getting 2 errors:
// Parsing error: Unexpected token, expected "]"
// ',' expected.
[id: String]: Number
},
required: true
},
Any ideas?
[id: String] is a TypeScript thing. Not allowed in standard JavaScript.
This is not technically possible in Dynamoose. The only option here is to use the saveUnknown schema setting.
This was brought up in a conversation before, and the user who wanted to do this I told to create an issue on the GitHub repo but it doesn't look like that happened. If you'd like support for this in Dynamoose in the future, please submit a feature request on the GitHub repo.
Edit
In order to do this your schema would looks something like:
new dynamoose.Schema({
result: {
type: Object,
required: true
},
}, {
"saveUnknown": ["result.**"]
});
This will allow indefinitely nested objects within result.

How do I add custom validators in JOI 17?

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());

Sequelize custom validation - unable to access all fields in entity

I attempted to create a model in sequelize (say has 3 attributes, attrA, B, and C) with some custom validation logic. This tutorial helped me get most of it set up:
const Model = Sequelize.define('model', {
attrA: { type: Sequelize.STRING },
attrB: { type: Sequelize.STRING },
attrC: { type: Sequelize.STRING },
}, {
validate: {
someValidationLogic() {
// Do something with attrA,B,C
// if (this.attrA ... this.attrB ... this.attrC) throw new Error..
}
}
})
In the application logic however, only say, 2 out of the 3 attributes (A and B) need to be updated:
Model.update(
{
attrA: 'foo',
attrB: 'bar'
}, {
where: {
id: 1,
},
returning: true,
})
This results in that when the custom validation logic being called, in the this object accessed in the function, only attrA and attrB are defined in this, and attrC remained undefined. This causes the validation logic to fail because attrC cannot be read. Is there any way I can get the object visible from someValidationLogic() to have all attributes populated? Or should this "validation" shouldn't have been validation logic at all and should've been done on the application level?
Your validation logic could take in account the possibility of attrC not being defined :
validate: {
someValidationLogic() {
if (this.attrA !== 'undefined' && this.attrA === 'forbidden value' ) {
// throw new Error
}
}
}
But if your validation includes checking the provided values against current database values, then you would better handle this in the application layer : first recover the current database record, manipulate it as needed, then save it to database.

How to make a virtual attribute for a field with select:false

I am using select:false to prevent the large amount of nested data from showing.
var Revision = {
Timestamp : { type : Date, default: Date.now },
};
var Article = new Schema({
Title: { type: String, required: true },
Revisions: {type: [Revision], select:false}
},schemaOptions);
And I want to use a virtual attribute to show it's small part.
Article
.virtual('LastRevision')
.get(function(){
var revs = this.Revisions; //undefined unless select:false is commented out
if ( revs && revs.length>0) {
return revs[revs.length-1];
}
});
Is there any way to access the field with select:false from the virtual attribute getter?
At the moment, you can only access select: false fields if you force select them when you make the query:
Article.findById(articleId, '+Revisions');
Note: don't use lean: true with queries where you also want the virtuals (this may help some people coming here).
More info/updates in this github issue.

Categories