Class validator with numbers and enum - validation

I want to create a DTO in nestjs using class-validator.
The value can accept a value between 1-24,trial or lifetime
I created an enum like this
export enum PeriodEnum {
"trial" = "trial",
"lifetime" = "lifetime"
}
And I tried to used this validation
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Min(0)
#Max(24)
#IsEnum(Object.keys(PeriodEnum))
period: string;
I get an error if I pass 1:
"period must be a valid enum value",
"period must not be greater than 10",
"period must not be less than 0"
I tried to add
"1" = "1"
But the "An enum member cannot have a numeric"
I tried even with RegExp
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Matches(`/^(${Object.keys(PeriodEnum)}|[1-9]|1[0-9]|2[0-4])$/`)
period: string;

I edited my DTO like this:
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Matches(`^(${Object.values(PeriodEnum).join("|")}|[1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4])$`)
period: string;
It works as I want now

since you are accepting strings, #Min and #Max no longer make sens. so here you need to deal with your numbers as string.
what I suggest is to create your own validation decorator :
export function IsLifetimeOrTrialOrBetween1and24(
validationOptions?: ValidationOptions
) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsLifetimeOrTrialOrBetween1and24",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any) {
// you manage your logic here then return either TRUE or FALSE
},
},
});
};
}
then use it like this :
#IsLifetimeOrTrialOrBetween1and24({
message: 'should be lifetime trial or a number between 1 and 24',
})
readonly period: string;
learn more here
but if you don't want to custom validation decorator this is an ugly solution but it works fine :
#IsIn(['trial', 'lifetime', '1', '2', '3', '4', '5', '6', '7',...,'23','24'])
readonly period: string;

Related

How to use class-validator validate Date is older than now and unique key in an array?

Using class-validator with Nest.js. I want to validate these two cases:
Validate the input date is older than now, then give a message: Date can't before than now.
#Field(() => Date, { description: 'Due Date' })
dueDate: Date;
Validate if all of the keys are unique in an array. But this way only can check if the ID is uuid. Is it possible to check if the IDs are the same in the array? Ex: ['1234-1234-1234-1234', '1234-1234-1234-1234']
#Field(() => [String], { description: 'product IDs' })
#IsUUID('all', { each: true, message: 'Product ID is not valid.' })
productIds: string[];
I searched and couldn't find a suitable inheriting validation decorators. You can custom validation classes like this:
#ValidatorConstraint()
export class IsAfterNowConstraint implements ValidatorConstraintInterface {
validate(date: Date) {
return Date.now() < date.getTime();
}
defaultMessage(args: ValidationArguments) {
return `Date ${args.property} can not before now.`;
}
}
function IsAfterNow(validationOptions?: ValidationOptions) {
// eslint-disable-next-line #typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsAfterNowConstraint,
});
};
}
#ArrayUnique(identifier?: (o) => any): Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion.
You can compare two dates with this function
import { ValidateBy, ValidationOptions, buildMessage, ValidationArguments } from 'class-validator'
export const IsAfter = (property: string, options?: ValidationOptions): PropertyDecorator =>
ValidateBy(
{
name: 'IsAfter',
constraints: [property],
validator: {
validate: (value: Date, args: ValidationArguments): boolean => {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as Record<string, unknown>)[relatedPropertyName] as Date
return value.toISOString() > relatedValue.toISOString()
},
defaultMessage: buildMessage((each: string): string => each + '$property must be after $constraint1', options),
},
},
options,
)
in this kind of situations
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly from!: Date
#IsAfter('from')
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly to!: Date

How to implement conditional Validation in Nested DTOs - NestJS?

I am working in NestJS Project, there is one situation where I need to implement conditional Validation.
So my Payload looks like this:
{
user_id: "123"
user_type: "M" //Value Can be M or F
inner_details: {
name: {
firstname:"akshay",
lastname:"nikte"
},
email: "ak#test.com"
},
user_co: "Tesla"
}
So in above payload(body) there are some validation requirement as below:
If user_type = M then Firstname and Lastname both cannot be Empty
If user_type = F then Firstname cannnot be Empty but Lastname can be Empty
For this I created 3 DTO classes:
Filename: create-user.dto.ts
export Class CreateUserDTO {
#IsNotEmpty()
#ApiProperty({
required:true
})
user_id: string
#IsNotEmpty()
#ApiProperty({
required:true
})
user_type: string
#ValidateNested({each:true})
#Type(()=>InnerDetailsDTO)
inner_details: InnerDetailsDTO
#IsNotEmpty()
user_co: string
}
filname: inner-details.dto.ts
export class InnerDetailsDTO {
#ValidateNested({each: true})
#Type: NameDTO
name: NameDTO
#IsNotEmpty
#ApiProperty({
required: true
})
email: string
}
filename name.dto.ts
export class NameDTO {
#IsNotEmpty()
#ApiProperty({required: true})
firstname: string
#Validateif(check if UserType==="M") // How to access UserType from CreateUser DTO
#IsNotEmprty()
lastname: string
}
I have have 3 different DTOs in 3 different Files, how to use attribute from 1 DTO in another DTO ?
In my NameDTO I want apply conditional validation, when user_type==M then validate both Firstname and Lastname but when user_type==F Then only validate Firstname and not Lastname
I recommend yup. There you can also make conditional validation. But therefore you have to setup yup as a Validation Pipeline in Nestjs as well. See this npm module here. But this package is not really popular, so I would implement it myself (I actually did that and it works pretty well). This video explains it really good.

How can I specify a Yup error on a validating field's parent schema?

I'm trying to set up a validation schema to use with Formik. I have a combobox that has items of shape { id: number; value: string; } and pulls the value out to display to the user, while submitting the whole item to Formik/Yup. However, in production, I won't know the shape of my items ahead of time; this is just the shape I've chosen for demoing.
const items = [
{ id: 1, value: 'foo' },
{ id: 2, value: 'bar' },
];
const [ field, meta, helpers ] = useField('value');
return (
<ComboBox
{...field}
invalid={meta.touched && !!meta.errors}
invalidText={meta.errors}
items={items}
itemToString={i => i?.value ?? ''}
onChange={data => helpers.setValue(data.selectedItem)}
selectedItem={field.value}
/>
);
I want to make only id = 1 to be valid.
const validationSchema = Yup.object({
value: Yup.object({
id: Yup.number().oneOf([1], "You've selected an invalid option."),
value: Yup.string(),
})
.required('You have not selected an option.')
.nullable(),
});
However, when this is in the error state, meta.errors is set to { id: "You've selected an invalid option." }. invalidText expects a ReactChild so when it receives this object, React throws an error.
I assume the solution, then, is to move .oneOf() to outside of the inner Yup.object(). I don't know how to specify the valid values, however, and neither the documentation nor a quick search of SO helped. Thoughts?

Angular2 Model Driven Dynamic Form Validation

Please Refer to my plunkr
I've been playing around with the new Angular 2 RC and I think I have figured out how the form validation works.
First I build 2 objects called defaultValidationMessages and formDefinition
private defaultValidationMessages: { [id: string]: string };
formDefinition: {
[fieldname: string]:
{
displayName: string,
placeholder: string,
currentErrorMessage: string,
customValidationMessages: { [errorKey: string]: string }
defaultValidators: ValidatorFn,
defaultValue: any
}
};
Then I load up those objects with the default validators and field information. and build the ControlGroup from the formDefinition object.
this.defaultValidationMessages = {
'required': '{displayName} is required',
'minlength': '{displayName} must be at least {minlength} characters',
'maxlength': '{displayName} cannot exceed {maxlength} characters',
'pattern': '{displayName} is not valid'
}
this.formDefinition = {
'name': {
displayName: 'Name',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose(
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(50)
]),
defaultValue: this.person.name
},
'isEmployee': {
displayName: 'Is Employee',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose([]),
defaultValue: this.person.isEmployee
},
'employeeId': {
displayName: 'Employee Id',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: { 'pattern': '{displayName} must be 5 numerical digits' },
defaultValidators: Validators.compose(
[
Validators.pattern((/\d{5}/).source)
]),
defaultValue: this.person.employeeId
}
}
this.personForm = this.formBuilder.group({});
for (var v in this.formDefinition) {
this.personForm.addControl(v, new Control(this.formDefinition[v].defaultValue, this.formDefinition[v].defaultValidators));
}
this.personForm.valueChanges
.map(value => {
return value;
})
.subscribe(data => this.onValueChanged(data));
Using a technique that I learned from Deborah Kurata's ng-conf 2016 session I bind a method to the ControlGroups valueChanges event.
By defining sets of default validators on each control it allows the control to dynamically append new validators to it based on future action. And then clearing back to the default validators later.
Issue I still have.
I was having an issue getting my typescript intellisense to import the ValidatorFn type. I found it here but I don't think I'm suppose to access it like this:
import { ValidatorFn } from '../../../node_modules/#angular/common/src/forms/directives/validators'
I also had to reset the form by setting some internal members. Is there a better way to reset the form? see below:
(<any> this.programForm.controls[v])._touched = false;
(<any> this.programForm.controls[v])._dirty = false;
(<any> this.programForm.controls[v])._pristine = true;
Please look at my plunk and let me know if there is a better way to handle model driven dynamic form validation?
My import string looks like this and it isn't marked as an error.
import { ValidatorFn } from '#angular/common/src/forms/directives/validators';
And some info about the reset form issue. There is not proper reset feature available yet, but a workaround exists. I've found it in docs.
You need a component field
active: true;
and you need to check it in your form tag:
<form *ngIf="active">
After that you should change your personFormSubmit() method to:
personFormSubmit() {
this.person = new Person();
this.active = false;
setTimeout(()=> {
this.active=true;
this.changeDetectorRef.detectChanges();
alert("Form submitted and reset.");
}, 0);
}
I tried this solution with you plnkr example and seems that it works.

How to use the names in a GraphQLEnumType as the defaultValue of a GraphQL query argument?

When defining a query in a schema, how do I refer to a value of an GraphQLEnumType declared previously, to use it as the default value of an argument?
Let's say I've defined following ObservationPeriod GraphQLEnumType:
observationPeriodEnum = new GraphQLEnumType {
name: "ObservationPeriod"
description: "One of the performance metrics observation periods"
values:
Daily:
value: '1D'
description: "Daily"
[…]
}
and use it as the type of query argument period:
queryRootType = new GraphQLObjectType {
name: "QueryRoot"
description: "Query entry points to the DWH."
fields:
performance:
type: performanceType
description: "Given a portfolio EID, an observation period (defaults to YTD)
and as-of date, as well as the source performance engine,
return the matching performance metrics."
args:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily ← how to achieve this?
[…]
}
Currently I'm using the actual '1D' string value as the default value; this works:
period:
type: observationPeriodEnum
defaultValue: '1D'
But is there a way I could use the Daily symbolic name instead? I couldn't find a way to use the names within the schema itself. Is there something I overlooked?
I'm asking, because I was expecting an enum type to behave as a set of constants also, and to be able to use them like this in the schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Naïve workaround:
##
# Given a GraphQLEnumType instance, this macro function injects the names
# of its enum values as keys the instance itself and returns the modified
# GraphQLEnumType instance.
#
modifiedWithNameKeys = (enumType) ->
for ev in enumType.getValues()
unless enumType[ ev.name]?
enumType[ ev.name] = ev.value
else
console.warn "SCHEMA> Enum name #{ev.name} conflicts with key of same
name on GraphQLEnumType object; it won't be injected for value lookup"
enumType
observationPeriodEnum = modifiedWithNameKeys new GraphQLEnumType {
name: "description: "Daily""
values:
[…]
which allows to use it as desired in schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Of course, this modifier fullfils its promise, only as long as the enum names do not interfere with GraphQLEnumType existing method and variable names (which are currently: name, description, _values, _enumConfig, _valueLookup, _nameLookup, getValues, serialize, parseValue, _getValueLookup, _getNameLookup and toString — see definition of GraphQLEnumType class around line 687 in https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L687)
I just ran into this. My enum:
const contributorArgs = Object.assign(
{},
connectionArgs, {
sort: {
type: new GraphQLEnumType({
name: 'ContributorSort',
values: {
top: { value: 0 },
},
})
},
}
);
In my queries, I was doing:
... on Topic {
id
contributors(first: 10, sort: 'top') {
...
}
}
Turns out you just don't quote the value (which after thinking about it makes sense; it's a value in the enum type, not an actual value:
... on Topic {
id
contributors(first: 10, sort: top) {
...
}
}
It's possible to declare enum values as default inputs via the schema definition language, but it looks like you are only using the JS library APIs. You might be able to get to a solution by taking a look at the ASTs for the working example and comparing that with the AST from what your JS code is producing.
Sorry not a solution, but hope that helps!
I found a pull request adding a method .getValue() to enum types, which returns name and value. In your case this call:
observationPeriodEnum.getValue('Daily');
would return:
{
name: 'Daily',
value: '1D'
}

Resources