Class-validator - validate array of objects - arrays

I am using class-validator package with NestJS and I am looking to validate an array of objects that need to have exactly 2 objects with the same layout:
So far I have:
import { IsString, IsNumber } from 'class-validator';
export class AuthParam {
#IsNumber()
id: number;
#IsString()
type: string;
#IsString()
value: string;
}
and
import { IsArray, ValidateNested } from 'class-validator';
import { AuthParam } from './authParam.model';
export class SignIn {
#IsArray()
#ValidateNested({ each: true })
authParameters: AuthParam[];
}
per #kamilg response (I am able to enforce exacly 2 elements):
import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';
export class SignInModel {
#IsArray()
#ValidateNested({ each: true })
#ArrayMinSize(2)
#ArrayMaxSize(2)
authParameters: AuthParam[];
}
I still can pass an empty array or an array with some other objects not related to AuthParam.
How I should modify it get validation?
Also how I can enforce mandatory 2 elements in the array? MinLength(2) seems to be regarding string... (resolved)

Add #Type(() => AuthParam) to your array and it should be working. Type decorator is required for nested objects(arrays). Your code becomes
import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';
import { Type } from 'class-transformer';
export class SignInModel {
#IsArray()
#ValidateNested({ each: true })
#ArrayMinSize(2)
#ArrayMaxSize(2)
#Type(() => AuthParam)
authParameters: AuthParam[];
}
Be careful if you are using any exception filter to modify the error reponse. Make sure you understand the structure of the class-validator errors.

I Know I Am Late But Facing Some Issue With Type, Then Try Another Way To Implement This:
export class AuthParam {
#IsNumber()
id: number;
#IsString()
type: string;
#IsString()
value: string;
}
Validation function
#ValidatorConstraint()
export class IsAuthArray implements ValidatorConstraintInterface {
public async validate(authData: AuthParam[], args: ValidationArguments) {
return Array.isArray(authData) && authData.reduce((a, b) => a && (typeof b.id === "number") && typeof b.type === "string" && typeof b.field === "string", true);
}
}
export class SignInModel {
#IsNotEmpty()
#IsArray()
#ArrayMinSize(2)
#ArrayMaxSize(2)
#Validate(IsAuthArray, {
message: "Enter valid value .",
})
authParameters: AuthParam[];
}
Maybe It Will Help Someone 😃

You can use the following:
validator.arrayNotEmpty(array); // Checks if given array is not empty.
validator.arrayMinSize(array, min); // Checks if array's length is at least `min` number.
(https://github.com/typestack/class-validator#manual-validation)
You may want to consider writing custom validator which would better reflect the business requirement you have.

const param1: AuthParam = Object.assign(new AuthParam(), {
id: 1,
type: 'grant',
value: 'password'
})
const param2: AuthParam = Object.assign(new AuthParam(), {
id: 1,
type: 4,
value: 'password'
})
const signInTest = new SignInModel()
signInTest.authParameters = [param1, param2]
validate(signInTest).then(e => {
console.log(e[0].children[0].children[0])
})
This works correctly, this is:
ValidationError {
target: AuthParam { id: 1, type: 4, value: 'password' },
value: 4,
property: 'type',
children: [],
constraints: { isString: 'type must be a string' } }
so I may only assume that object which is being validated, is not an instance of AuthParam
const param2: AuthParam = {
id: 1,
type: 4,
value: 'password'
} as any
as expected, there aren't any decorators on this object (which may be true for Nest.js controllers and nested objects from body/req) - so validation is ignored.
Please check this (tl;dr - #Type decorator form class-transformer)

Related

2d object whose key is the same as the keys of a value in the same object

I'm still a newbie in typescript and been searching in google about this for almost 2 hours now.
I want to create a type that is an object that will contain multiple values. The key of one value in the object would be the same as the keys of another value in that same object, except this time they will be functions.
Example value of this would be:
{
values: {
email: '',
password: ''
},
validators: {
email: () => { /* some function that does something and returns a string */ },
password: () => { /* some function that does something and returns a string */ },
}
};
In the example above, you see that the keys of validators are the same as the keys of values, except validators are composed of functions.
I can do:
export type Options = {
values: Record<string, unknown>,
validators: Record<string, unknown>
};
but this is not robust because I want the autocomplete to happen, I want validators to be a record which keys are the same as the keys of values, except all those are optional and should be functions.
I'm thinking of using generics, but I don't know what to pass to it
type FormValidator<T> = {
[P in keyof T]?: (values: Record<string, unknown>) => string;
};
export type FormOptions = {
values: Record<string, unknown>;
validators?: FormValidator<this.values>; // WHAT DO I PASS HERE SO THAT IT WILL GET THE KEYS OF VALUES?
};
EDIT 1:
One thing that I think works the same as I want this to work is this:
https://www.typescriptlang.org/play?#code/C4TwDgpgBAYg9gJwLYDUCGAbAlgEzcRAHgBUA+KAXigG8BYAKCigG0AFKLAOygGsIQ4AMyjEAugC4oACgCUlcgGdgCLgHMA3AwC+m+gwDGcTkqgA3TAFcICyjQZMmEJGiwZJAcncAaew7BoFBQB3RBwPd21dAyMTc2w8AgQbSXhkdHj8IlBIITNLa3IqOkYHSKA
Notice how the autocomplete works, and if you add something else that's not in values, it will also throw an error that's basically how I want it but I want it defined in that one type: type FormOptions
EDIT 2:
I'm wanting to do this in ReactJS / ReactNative and just plain NodeJS/TypeScript (that is, without using any frameworks)
Not sure if your question right but here's my example using type parameters
Sample snippet
// interface
interface FormParamConfigMap {
[paramName: string]: any;
}
// type parameter
const Form = <FormParamMap extends FormParamConfigMap>(options: {
values: FormParamMap;
filter: FormParamMap;
}): any => {};
///// usage
const formIntance = Form({
values:{
email:'',
password:''
},
filter:{
email:()=>'valid',
password: ()=>'valid'
}
})
It looks like you want compile time type inference (so auto-complete).
In that case we can't use this or typeof, etc.
And you want pre-defined key set (indexer) which should be shared by both values.
For this, I don't think there is any way other than string literals.
Such as:
type RecordKeys = 'email' | 'password';
type Options = {
values: Record<RecordKeys, string>,
validators: Record<RecordKeys, (values: RecordKeys) => string>
};
const o: Options = {
values: {
email: '',
password: ''
},
validators: {
email: () => { return 'OK'; },
password: () => { return 'OK'; },
}
};
This guide is very helpful: https://basarat.gitbook.io/typescript/type-system/index-signatures
I believe you have two options here:
Pass all keys as a generic parameter:
type Options<T extends string> = {
value: Record<T, string>,
validators: Partial<Record<T, () => string>>
}
type Keys = 'foo' | 'bar'
const options: Options<Keys> = {
value: {
foo: '',
bar: ''
},
validators: {
foo: () => 'foo',
bar: () => 'bar'
}
}
OR infer the type from function argument
const isValid = <Keys extends string>(options: Options<Keys>) => {}
isValid({
value: {
a: 'a',
},
validators: {
a: () => 'a'
}
}) // ok
isValid({
value: {
b: 'a', // error
},
validators: {
a: () => 'a'
}
}) // ok
Playground

complex query in typeorm

hello every body i have entity that contain special property used for translation, when i try to get this special property usin typeorm i have this error: (node:3712) UnhandledPromiseRejectionWarning: QueryFailedError: syntax error at or near "[">>>
the entity is:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BaseEntity,
OneToOne,
JoinColumn,
} from "typeorm";
import {TextData} from "./TextData"
#Entity()
export class ContactUsDataNature extends BaseEntity {
#PrimaryGeneratedColumn()
id:number
#OneToOne((type) => TextData, (text) => text.id, { cascade: true })
#JoinColumn()
dataNature:TextData
}
the text data is:
import { Entity, Column, PrimaryGeneratedColumn, BaseEntity } from "typeorm";
#Entity()
export class TextData extends BaseEntity{
#PrimaryGeneratedColumn()
id: number;
#Column({ type: "text", nullable: true })
ar: string;
#Column({ type: "text", nullable: true })
en: string;
}
i usually reach it like
const contact=new ContactUSDataNature()
console.log(contact.dataNature[en])
i tried the way to search but it doesn't work :
export const getContactDataNaturebyData = async (dataNature: string,language="en") =>{
const container=await ContactUsDataNature.find({
where: (qb) => {
if (true) {
qb.andWhere("ContactUsDataNature__dataNature.:lan = :parentId", {
lan:"en",
parentId: 1,
});
}
},
order: { id: "DESC" },
skip: 10,
take: 0,
});
console.log(container)
return container
}
I need to get search in database for ContactUsDataNature if i get the final string value
You cannot parameterize the column names in a query.
You can build the where by concatenating the column name:
qb.andWhere("ContactUsDataNature__dataNature." + lan + " = :parentId", { parentId: 1 });
When you test this, I recommend that you turn on TypeOrm full logging so you can see the actual generated SQL and you be able to quickly solve any problems. See TypeOrm logging.

How can I solve this error 'don't use object as a type'?

I do not understand this error message caused.
My component has two and one data array which has objects.
I got an error message 'Don't use object as a type. The object type is currently hard to use'.
How can i solve it ??
I attached the data which array has objects.
first.tsx...
import { dataList } from './xxx';
// This dataList ===
export const dataList = [
{
id: 1,
title: "text",
hidden1: "text",
},
{
id: 2,
title: "text",
hidden1: "text",
hidden2: "text",
},
{
id: 3,
title: "text",
hidden1: "text",
hidden2: "text",
hidden3: "text",
},
];
const First = () => {
return <Second data={dataList} />
}
second.tsx...
const Second = ({data} : { data:object[] } ) => {. << here, "Don't use 'object' as a type.
return <ul><li> {data.tag....blah blah} </li><ul>
}
error message :
Don't use `object` as a type. The `object` type is currently hard to use ([see this issue](https://github.com/microsoft/TypeScript/issues/21732)).
Consider using `Record<string, unknown>` instead, as it allows you to more easily inspect and use the keys #typescript-eslint/ban-types
You need to be explicit with the type of the data being destructured here.
You can just create an interface of the data.
Edit:
As your data has some optional parameters I updated the interface to reflect that.
interface IData {
id: number;
title: string;
hidden1: string;
hidden2?: string;
hidden3?: string;
}
Then replace object[] with IData[]. So now you specify that this takes in an array of the IData interface.
const Second = ({ data } : { data:IData[] }) => {
return <ul><li>{data}</li><ul>
}
I did not try this with React, so a bit different code style here, but Typescript compiles this even without creating an interface:
const second = (data: Record<string, unknown>[]) => {
return data
.map(item =>
Object.keys(item)
.map(key => `key:${key} = value:${item[key]}`)
.join(',')
)
.join(';');
};
I built in some mapping, but you could just return data too. Which of course would be useless, but it would compile.
This would compile too. Notice the array [] symbol in both solutions:
const second = (data: { [key: string]: unknown }[]) => {
return data
.map(item =>
Object.keys(item)
.map(key => `key:${key} = value:${item[key]}`)
.join(',')
)
.join(';');
};

Trying to figure out how to switchMap an Organization to a Base in an Angular Component

So I have an API that I am trying to consume data from. The data being retuned:
Org
baseId: 1
createdAt: "2018-11-14T04:35:35.345Z"
id: 1
isActive: true
orgName: "Test Organization 1"
orgOwner: 2
subscriptionCode: "testorg1"
updatedAt: "2018-11-14T04:35:35.34
Base
bandwidthApiSecret: "xxxxxx"
bandwidthApiToken: "t-xxxxxx"
bandwidthUserId: "u-xxxxxx"
baseName: "Test AFB 1"
basePhoneNumber: "+18442367381"
createdAt: "2018-11-14T04:35:35.123Z"
id: 1
isActive: true
updatedAt: "2018-11-14T04:35:35.123Z"
What I would like to do, but fail to comprehend is using rxjs operations to insert the organization where needed. So if a base has one or more organizations it should be inserted into the bases. Here are my interfaces:
Base Interface
import { Organization } from './organization';
import { BaseManager } from './base-manager';
export interface Base {
id: number;
basePhoneNumber: string;
baseName: string;
bandwidthUserId: string;
bandwidthApiToken: string;
bandwidthApiSecret: string;
createdAt?: Date;
updatedAt?: Date;
orgs?: Organization;
managers?: BaseManager;
}
Org Interface
export interface Organization {
id: number;
orgName: string;
orgOwner: string;
baseId: number;
subscriptionCode: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
I found this JSBin that sort of looks like it's doing what I need to accomplish. Due to my lack of comprehension of the subject I am failing to make it work. Here's a snippet of my Angular Component:
import { Component, OnInit } from '#angular/core';
import { Base } from 'src/app/core/interfaces/base';
import { BaseService } from 'src/app/core/services/base.service';
import { OrgService } from 'src/app/core/services/org.service';
import { Organization } from 'src/app/core/interfaces/organization';
import { switchMap } from 'rxjs/operators';
#Component({
selector: 'app-base-list',
templateUrl: './base-list.component.html',
styleUrls: ['./base-list.component.css']
})
export class BaseListComponent implements OnInit {
bases: Base[];
orgs: Organization[];
constructor(private baseService: BaseService, private orgService: OrgService) { }
ngOnInit() {
this.loadOrgs();
this.loadBases();
}
loadBases() {
this.baseService.getAllBases()
.subscribe(
res => {
this.bases = res['bases'];
console.log(this.bases);
}
);
}
// loadBases(): Base[] {
// this.baseService.getAllBases()
// .subscribe(
// res => {
// this.bases = res['bases'].pipe(
// switchMap( base => {
// return this.getOrg(base.id).map(
// base => {
// return Object.assign(base, {base, {org: this.orgs});
// }
// )
// })
// );
// });
// }
loadOrgs() {
this.orgService.getOrgs()
.subscribe(
res => {
this.orgs = res['orgs'];
console.log(this.orgs);
}
);
}
getOrg(baseId) {
this.bases.filter(base => base.id === baseId);
}
}
Any help on the matter would be greatly appreciated.
Cheers,
Coach
There are three issues: First, res['bases'] is not an Observable and therefore can't be piped. This function invocation is not possible:
res['bases'].pipe(...)
Second, you are trying to assign the Observable to the property this.bases which has the type Base[] and not Observable<Base[]>. The assignment would therefore not be valid.
Third, even if res['bases'] were an Observable, you are never subscribing to it, so it won't ever emit any notifications.
The solution is one the one hand to not subscribe before the switchMap, but afterwards and on the other hand to correctly handle the array collection, e.g. with zip. This is how it would look like:
this.baseService.getAllBases().pipe(
switchMap(bases =>
zip(...bases.map(base =>
this.getOrg(base.id).pipe(
map(base => Object.assign(base, {orgs: this.orgs}))
)
)
)
)
).subscribe(bases =>
this.bases = bases
)
Now, another issue is that you aren't waiting for loadOrgs to compete before trying to load the bases. This should also be concatenated like this:
loadOrgs(): Observable<Organization[]> {
return this.orgService.getOrgs().pipe(
tap(
res => {
this.orgs = res['orgs'];
console.log(this.orgs);
}
)
)
}
And in ngOnInit:
ngOnInit() {
this.loadOrgs().subscribe(() => {
this.loadBases();
});
}
As a rule of thumb, be sure to always subscribe to your Observables, otherwise you won't actually execute anything. There are some pipes that do subscribe to inner Observables, like switchMap, but there must actually be a Subscriber for the switchMap pipe as well in order for it to take effect.

My query is failing in relay and I don't know why?

I have this simple query which works fine in my Graphql but I cannot pass data using relay to components and I don't know why :(
{
todolist { // todolist returns array of objects of todo
id
text
done
}
}
this is my code in an attempt to pass data in components using relay:
class TodoList extends React.Component {
render() {
return <ul>
{this.props.todos.todolist.map((todo) => {
<Todo todo={todo} />
})}
</ul>;
}
}
export default Relay.createContainer(TodoList, {
fragments: {
todos: () => Relay.QL`
fragment on Query {
todolist {
id
text
done
}
}
`,
},
});
And lastly my schema
const Todo = new GraphQLObjectType({
name: 'Todo',
description: 'This contains list of todos which belong to its\' (Persons)users',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve: (todo) => {
return todo.id;
}
},
text: {
type: GraphQLString,
resolve: (todo) => {
return todo.text;
}
},
done: {
type: GraphQLBoolean,
resolve: (todo) => {
return todo.done;
}
},
}
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'This is the root query',
fields: () => {
return {
todolist: {
type: new GraphQLList(Todo),
resolve: (root, args) => {
return Conn.models.todo.findAll({ where: args})
}
}
}
}
});
This code looks simple and I cannot see why this won't work and I have this error Uncaught TypeError: Cannot read property 'todolist' of undefined, but I configure todolist and I can query in my graphql, you can see the structure of the query is same, I don't know why this is not working?
todolist should be a connection type on Query. Also, your ids should be Relay global IDs. You will not have access to your objects' raw native id fields in Relay.
import {
connectionArgs,
connectionDefinitions,
globalIdField,
} from 'graphql-relay';
// I'm renaming Todo to TodoType
const TodoType = new GraphQLObjectType({
...,
fields: {
id: uidGlobalIdField('Todo'),
...
},
});
const {
connectionType: TodoConnection,
} = connectionDefinitions({ name: 'Todo', nodeType: TodoType });
// Also renaming Query to QueryType
const QueryType = new GraphQLObjectType({
...,
fields: {
id: globalIdField('Query', $queryId), // hard-code queryId if you only have one Query concept (Facebook thinks of this top level field as being a user, so the $queryId would be the user id in their world)
todos: { // Better than todoList; generally if it's plural in Relay it's assumed to be a connection or list
type: TodoConnection,
args: connectionArgs,
},
},
});
// Now, to be able to query off of QueryType
const viewerDefaultField = {
query: { // Normally this is called `viewer`, but `query` is ok (I think)
query: QueryType,
resolve: () => ({}),
description: 'The entry point into the graph',
}
};
export { viewerDefaultField };
The above is not fully complete (you'll likely also need to setup a node interface on one or more of your types, which will require node definitions), but it should answer your basic question and get you started.
It's a huge, huge pain to learn, but once you struggle through it it starts to make sense and you'll begin to love it over RESTful calls.

Resources