Validate each Map<string, number> value using class-validator - validation

I'm trying to perform simple validation on a JSON input, modelled by one of my DTOs.
One of the object properties is of type Map<string, number>. an example input:
{
"type": "CUSTOM",
"is_active": true,
"current_plan_day": 1,
"custom_warmup_plan": {
"1": 123,
"2": 456
}
On my controller I'm using a DTO to specify the body type. the class, together with class-validator decorators is this:
export class CreateWarmupPlanRequestDto {
#IsEnum(WarmupPlanType)
type: string;
#IsOptional()
#IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
#IsPositive()
hard_cap: number | null;
#IsBoolean()
is_active: boolean;
#IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
#IsPositive()
current_plan_day: number;
#IsOptional()
#IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
#IsPositive()
previous_plan_day: number | null;
#IsOptional()
#IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 }, { each: true })
#IsPositive({ each: true })
custom_warmup_plan: Map<string, number>; // PROBLEM HERE
}
I'm looking to validate each value of custom_warmup_plan to be an existing positive integer.
Validation of the other properties of the object works just fine and as expected, but for my example input I keep getting errors (2 error messages, joined):
{
"message": "each value in custom_warmup_plan must be a positive number. |#| each value in custom_warmup_plan must be a number conforming to the specified constraints",
"statusCode": 400,
"timestamp": "2021-07-29T13:18:29.331Z",
"path": "/api/warmup-plan/bc4c3f0e-8e77-46de-a46a-a908edbdded5"
}
Documentation for this seems to be pretty straight forward, but I just cant get it to work.
I've also played around with a simple Map<string, string> and the #IsString(each: true) validator, but that does not seem to work either.
any ideas?
versions:
"#nestjs/common": "^8.0.0",
"#nestjs/core": "^8.0.0",
"#nestjs/mapped-types": "^1.0.0",
"#nestjs/platform-express": "^8.0.0",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",

It is necessary to convert plain object to map. Use Transform decorator from class-transformer
#IsOptional()
#IsNumber(undefined, { each: true })
#Transform(({ value }) => new Map(Object.entries(value)))
prop?: Map<string, number>;

From the docs
If your field is an array and you want to perform validation of each item in the array you must specify a special each: true decorator option
If you want to be able to validate maps you could write a custom decorator and pass in a list of class-validator functions to validate the keys and values. For example the below decorator takes as input a list of validation functions for both the keys and values (e.g. passing in isString, isObject, etc..., class-validator has a corresponding function you can call for all the validation decorators they provide)
export function IsMap(
key_validators: ((value: unknown) => boolean)[],
value_validators: ((value: unknown) => boolean)[],
validationOptions?: ValidationOptions
) {
return function (object: unknown, propertyName: string) {
registerDecorator({
name: 'isMap',
target: (object as any).constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: unknown, args: ValidationArguments) {
if (!isObject(value)) return false;
const keys = Object.keys(value);
const is_invalid = keys.some((key) => {
const is_key_invalid = key_validators.some((validator) => !validator(key));
if (is_key_invalid) return false;
const is_value_invalid = value_validators.some((validator) => !validator(value[key]));
return is_value_invalid;
});
return !is_invalid;
},
},
});
};
}
And you can use this decorator in your example like this
import { isInt } from 'class-validator'
export class CreateWarmupPlanRequestDto {
#IsOptional()
#IsMap([], [isInt])
custom_warmup_plan: Map<string, number>;
}

Using the same approach with #Daniel, I modified the code little bit so that the focus is on 'isValid' rather than 'IsInvalid'. So that we could avoid double negation.
Additionally, the coming object is transformed to map in the DTO.
#Transform(({ value }) => new Map(Object.entries(value)))
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
import * as $$ from 'lodash';
export function IsMap(
keyValidators: ((value: unknown) => boolean)[],
valueValidators: ((value: unknown) => boolean)[],
validationOptions?: ValidationOptions,
) {
return function (object: unknown, propertyName: string) {
/**
* ** value is expected to be a MAP already, we are just checking types of keys and values...
*/
registerDecorator({
name: 'isMap',
target: (object as any).constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: Map<any, any>, args: ValidationArguments) {
if (!$$.isMap(value)) {
return false;
}
const keys = Array.from(value.keys());
return $$.every(keys, (key) => {
// checking if keys are valid...
const isKeyInvalid = keyValidators.some(
(validator) => !validator(key),
);
if (isKeyInvalid) {
return false;
}
// checking if values are valid...
const isValueInvalid = valueValidators.some(
(validator) => !validator(value.get(key)),
);
if (isValueInvalid) {
return false;
} else {
return true;
}
});
},
},
});
};
}

Related

Make an ajax call inside of a .map()

I am upgrading jquery and saw that the "async: false" option has been deprecated. This makes sense and in 99.9% of cases I agree with the rationale, but I have a case where I think I really need it and I cannot for the life of me figure out how to make this work with a purely async ajax call no matter how I use promises or async/await.
My use case is in a Vue component and I have an array of contacts. What I need to do is map over the contacts and validate them. One such validation requires a quick check of email validity via a "check_email" ajax endpoint.
Once I validate (or not) the list, I then submit the list (if valid) or show error messages (if invalid).
My code is something like this
sendContacts: function() {
valid = this.validateContacts()
if (valid) {
// send the contacts
} else {
return // will display error messages on contacts objects
}
},
validateContacts: function() {
this.contacts = this.contacts.map((contact) => {
if (!contact.name) {
contact.validDetails.name = false
contact.valid = false
return contact
}
if (!contact.email) {
contact.validDetails.emailExists = false
contact.valid = false
return contact
}
if (!check_email(email)) { // THIS IS ASYNC NOW WHAT DO I DO
contact.valid = false
contact.validDetails.emailFormat = false
}
return contact
}
var validData = this.contacts.map(c => {
return c.valid
})
return !validData.includes(false)
}
function check_email(email) {
const url = `/api/v1/users/check-email?email=${email}`
let valid = false
$.ajax({
url: url,
type: 'POST',
async: false, // I can't do this anymore
headers: {
'X-CSRFToken': csrfToken
},
success: resp => {
valid = true
},
error: err => {
}
})
return valid
}
my data function:
data: function() {
return {
contacts: [this.initContact()],
showThanks: false,
emailError: false,
blankEmail: false,
blankName: false
}
},
methods: {
initContact: function() {
return {
name: null,
email: null,
title: null,
validDetails: this.initValidDetails(),
valid: true,
}
},
initValidDetails: function() {
return {
emailDomain: true,
emailExists: true,
emailFormat: true,
name: true
}
}
}
Again, I have tried async/await in every place I could think of and I cannot get this to validate properly and then perform correct logic regarding whether the send contacts function part of the function should fire. Please help!
Once any part of your validation is asynchronous, you must treat the entire thing as asynchronous. This includes when calling validateContacts in sendContacts.
First, you should change check_email to return Promise<bool>. It's usually a bad idea to include jQuery in a Vue project so let's use fetch instead (Axios being another popular alternative).
async function check_email(email) {
const params = new URLSearchParams({ email })
const res = await fetch(`/api/v1/users/check-email?${params}`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
}
})
return res.ok
}
As for your async validation logic, it's best to map your contacts to an array of promises and wait for them all with Promise.all.
async validateContacts () {
const validationPromises = this.contacts.map(async contact => {
if (!contact.name) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
name: false
}
}
}
if (!contact.email) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailExists: false
}
}
}
if (await check_email(contact.email)) { // await here
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailFormat: false
}
}
}
return { ...contact, valid: true }
})
// now wait for all promises to resolve and check for any "false" values
this.contacts = await Promise.all(validationPromises)
return this.contacts.every(({ valid }) => valid)
}
As mentioned, now you need to treat this asynchronously in sendContacts
async sendContacts () {
if (await this.validateContacts()) {
// send the contacts
}
}

How to do a nested mutation resolver with nexus-prisma

I have the following datamodel:
type Job {
// ...
example: String
selections: [Selection!]
// ...
}
type Selection {
...
question: String
...
}
I define my object type so:
export const Job = prismaObjectType({
name: 'Job',
definition(t) {
t.prismaFields([
// ...
'example',
{
name: 'selections',
},
// ...
])
},
})
I do my resolver this way:
t.field('createJob', {
type: 'Job',
args: {
// ...
example: stringArg(),
selections: stringArg(),
// ...
},
resolve: (parent, {
example,
selections
}, ctx) => {
// The resolver where I do a ctx.prisma.createJob and connect/create with example
},
})
So now in the resolver I can receive the selections as json string and then parse it and connect/create with the job.
The mutation would look like this:
mutation {
createJob(
example: "bla"
selections: "ESCAPED JSON HERE"
){
id
}
}
I was wondering if there's anything more elegant where I could do something like:
mutation {
createJob(
example: "bla"
selections: {
question: "bla"
}
){
id
}
}
or
mutation {
createJob(
example: "bla"
selections(data: {
// ...
})
){
id
}
}
I've noticed that with nexus-prisma you can do stringArg({list: true}) but you can't really do objects.
My main question is what is the most elegant way to do either nested mutation or connect all in one.
You can use an inputObjectType as shown in the docs:
export const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
t.int("priority");
},
});
Make sure to include the type as part of the types you pass to makeSchema. You can then use it to define an argument, like
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
}
Now, the argument value will be available to your resolver as a regular JavaScript object, not a String. If you need a list of input objects, or want to make the argument required, you do so using the same options you would provide with when using a scalar -- list, nullable, description, etc.
Here's a complete example:
const Query = queryType({
definition(t) {
t.field('someField', {
type: 'String',
nullable: true,
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
},
resolve: (parent, { input }) => {
return `You entered: ${input && input.name}`
},
})
},
})
const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
},
});
const schema = makeSchema({
types: {Query, SomeFieldInput},
outputs: {
...
},
});
Then query it like:
query {
someField(
input: {
name: "Foo"
}
)
}
Or using variables:
query($input: SomeFieldInput) {
someField(input: $input)
}

I need help understanding Relay OutputFields, getFatQuery

This is the code from official docs of relay, This is for GraphQLAddTodoMutation
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({localTodoId}) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(getTodos(), todo),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localTodoId = addTodo(text);
return {localTodoId};
},
});
I think mutateAndGetPayload executes first then outputFields? since it used localTodoId object as parameter, I see localTodoId object returned from mutateAndGetPayload.
and this is the code for relay mutation.please look at the getFatQuery
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
return Relay.QL`mutation{addTodo}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTodoPayload #relay(pattern: true) {
todoEdge,
viewer {
todos,
totalCount,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'todos',
edgeName: 'todoEdge',
rangeBehaviors: ({status}) => {
if (status === 'completed') {
return 'ignore';
} else {
return 'append';
}
},
}];
}
getVariables() {
return {
text: this.props.text,
};
}
getOptimisticResponse() {
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
todoEdge: {
node: {
complete: false,
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
I think the todoEdge is from the outputFields from GraphQL? I see a viewer query on it, why does it need to query the viewer? How do I create a getFatQuery? I would really appreciate if someone help me understand this more and about Relay mutation.
mutateAndGetPayload executes then returns the payload to the outputFields
mutationWithClientMutationId
Source-Code
starWarsSchema example
mutationWithClientMutationId
inputFields: defines the input structures for mutation, where the input fields will be wraped with the input values
outputFields: defines the ouptput structure of the fields after the mutation is done which we can view and read
mutateAndGetPayload: this function is the core one to relay mutations, which performs the mutaion logic (such as database operations) and will return the payload to be exposed to output fields of the mutation.
mutateAndGetPayload maps from the input fields to the output fields using the mutation
operation. The first argument it receives is the list of the input parameters, which we can read to perform the mutation action
The object we return from mutateAndGetPayload can be accessed within the output fields
resolve() functions as the first argument.
getFatQuery() is where we represent, using a GraphQL fragment, everything
in our data model that could change as a result of this mutation

Angular 2 Custom Validator with Observable Parameter

I have this custom validator:
export const mealTypesValidator = (mealSelected: boolean) => {
return (control: FormControl) => {
var mealTypes = control.value;
if (mealTypes) {
if (mealTypes.length < 1 && mealSelected) {
return {
mealTypesValid: { valid: false }
};
}
}
return null;
};
};
If I use it like this it works:
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [[], mealTypesValidator(true)],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
The catch is, mealSelected is a property on my component - that changes when the user selects and deselects a meal.
How I call the validator above is using static true which can never change.
How can I get the validator to work when I use the component.mealSelected value as the parameter eg:
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [[], mealTypesValidator(this.mealSelected)],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
Because if i do it as above, it evaluates this.mealSelected instantly which is false at the time - and then when the user selects a meal, it doesn't then go ahead and pass true into the custom validator.
Solution was to move the validator inside my component and use this.mealSelected to check against. Then I had an issue with the validator not being triggered when a meal was selected/deselected and I used this.findForm.controls['mealTypes'].updateValueAndValidity(); to trigger the validation.
Code (can probably be refactored to remove the parameter from the custom validator):
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [[], this.mealTypesValidator(true)],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
mealTypesValidator = (mealSelected: boolean) => {
return (control: FormControl) => {
var mealTypes = control.value;
if (mealTypes) {
if (mealTypes.length < 1 && this.mealSelected) {
return {
mealTypesValid: { valid: false }
};
}
}
return null;
};
};
However It would still be nice to be able to have a seperate validation module to centralise validation, so if anyone knows how to have a changing parameter value such as a component field as a parameter to a custom validator - like I initially asked, then I'd appreciate an answer that goes with that technique.

How to know which attribute called the waterline validation rule?

I'm doing my own custom validations on certain fields, so that only certain values are accepted (depending on the field) and the rest rejected. I would like to write a "filter" function that checks what attribute called the validation and from there decide what words the attribute is allowed to use. So the model would look something like this:
module.exports = {
types: {
filter: function(attribute) {
if (attribute === 'number') {
switch(attribute.value) {
case 'one':
return true;
case 'two':
return true;
default:
return false;
}
} else if (attribute === 'color') {
switch(attribute.value) {
case 'red':
return true;
case 'blue':
return true;
default:
return false;
}
}
},
},
attributes: {
number: {
type: 'string',
required: true,
filter: true
},
color: {
type: 'string',
required: true,
filter: true
}
}
};
Of course, in normal Sails.js behaviour, "attribute" would not be the attribute, but the value of the attribute. (And attribute.value was just an example, meaning, I want the attribute value in there).
So, I want attribute to be the actual attribute that called the validation rule. Is this possible with Sails? I mean, I could write a function for each field in the model, but it would be nice to have a function that fits them all (I have many of them).
Thanks.
Ok so I will answer your question, but this may not be exactly what you want. An attribute can have an "enum" which is how we'd achieve your end goal:
attributes: {
state: {
type: 'string',
enum: ['pending', 'approved', 'denied']
}
}
But I will assume that this code is just a contrived example. Here's a way that I think would work.
module.exports = {
types: {
filter: function(attribute) {
if (attribute === 'number') {
switch(attribute.value) {
case 'one':
return true;
case 'two':
return true;
default:
this.validationErrors.push(attribute);
return false;
}
} else if (attribute === 'color') {
switch(attribute.value) {
case 'red':
return true;
case 'blue':
return true;
default:
this.validationErrors.push(attribute);
return false;
}
}
},
},
attributes: {
validationErrors:(function(){
var errors = [];
return {
push:function(attr){
errors.push(attr);
},
get:function(){
return errors;
}
};
})(),
number: {
type: 'string',
required: true,
filter: true
},
color: {
type: 'string',
required: true,
filter: true
}
}
};
Edit:Used an attribute method instead of an attribute
There are potentially a couple problems with this answer. I'm not sure how waterline handles "this" within these custom type functions. Is "this" bound to the model? Or the instance of the model we're creating? There's a lot of questions to be asked here but maybe this can give you some ideas and create a discussion.

Resources