NestJS GraphQL custom argument type - graphql

I'm trying to use LocalDate type from js-joda as parameter on GraphQL query like this:
#Query(() => DataResponse)
async getData(#Args() filter: DataFilter): Promise<DataResponse> { ... }
And here is filter type definition:
#ArgsType()
export class DataFilter {
#Field({ nullable: true })
#IsOptional()
date?: LocalDate;
#Field()
#Min(1)
page: number;
#Field()
#Min(1)
pageSize: number;
}
I've also registered LocalDate as scalar type and added it to application providers.
#Scalar('LocalDate', (type) => LocalDate)
export class LocalDateScalar implements CustomScalar<string, LocalDate> {
description = 'A date string, such as 2018-07-01, serialized in ISO8601 format';
parseValue(value: string): LocalDate {
return LocalDate.parse(value);
}
serialize(value: LocalDate): string {
return value.toString();
}
parseLiteral(ast: ValueNode): LocalDate {
if (ast.kind === Kind.STRING) {
return LocalDate.parse(ast.value);
}
return null;
}
}
This is the error I'm getting
[Nest] 9973 - 02/16/2022, 5:33:41 PM ERROR [ExceptionsHandler] year
must not be null NullPointerException: year must not be null
at requireNonNull (/Users/usr/my-app/node_modules/#js-joda/core/src/assert.js:33:15)
at new LocalDate (/Users/usr/my-app/node_modules/#js-joda/core/src/LocalDate.js:284:9)
at TransformOperationExecutor.transform (/Users/usr/my-app/node_modules/src/TransformOperationExecutor.ts:160:22)
at TransformOperationExecutor.transform (/Users/usr/my-app/node_modules/src/TransformOperationExecutor.ts:333:33)
at ClassTransformer.plainToInstance (/Users/usr/my-app/node_modules/src/ClassTransformer.ts:77:21)
at Object.plainToClass (/Users/usr/my-app/node_modules/src/index.ts:71:27)
at ValidationPipe.transform (/Users/usr/my-app/node_modules/#nestjs/common/pipes/validation.pipe.js:51:39)
at /Users/usr/my-app/node_modules/#nestjs/core/pipes/pipes-consumer.js:17:33
at processTicksAndRejections (node:internal/process/task_queues:96:5)
I'm not sure why is this exactly happening but from what I've managed to debug, is that LocalDateScalar defined above is transforming the value from string to LocalDate correctly, but the problem is that class-transformer is also trying to transform the value, and since it's already transformed it recognizes it as object, which is automatically being call through parameterless constructor and it's causing this error.
This is the line from class-transformer that's calling the constructor
newValue = new (targetType as any)();
Is there maybe a way to tell class-transformers which types to ignore? I'm aware of the #Exclude attribute, but then property is completely excluded, I just need to exclude property being transformed via plainToClass method of class-transformer. Or this whole situation should be handled differently?
Any suggestion will be well appreciated.

Not sure if this is the right solution but I had a similar scalar <string, Big> working with the following decorators:
#Field(() => AmountScalar) // your actual scalar class
#Type(() => String) // the "serialized" type of the scalar
#Transform(({ value }) => {
return Big(value) // custom parse function
})
amount: Big // the "parsed" type of the scalar
The two custom parse functions in the scalar can also contain some validation steps (like moment.isValid() in your case) since it will be called before class-validator.

Related

Resolve field returns null for all fields except ID (extended type)

I have original GQL type from intermediaries microservice.
#ObjectType('Intermediary')
#Directive('#key(fields: "id")')
export class IntermediaryType implements Intermediary {
#Field(() => ID)
id: string
#Field()
beneficiaryName: string
// and other fields
}
And another GQL type that extends from external GQL type.
#ObjectType('Intermediary')
#Directive('#extends')
#Directive('#key(fields: "id")')
export class IntermediaryType {
#Field(() => ID)
#Directive('#external')
id: string
}
GQL type that nests external GQL type:
#ObjectType('FXPayment')
export class FXPaymentType {
#Field(() => ID)
id: string
#Field(() => [IntermediaryType])
intermediaries?: IntermediaryType[]
// and other fields
}
And I have a resolver for my mutation and intermediaries field.
#Resolver(() => FXPaymentType)
export class FXPaymentsResolver {
constructor(private _fxPaymentsService: FXPaymentsService) {}
#Mutation(() => FXPaymentType)
async createFXPayment(
#Args('input') input: CreateFXPaymentInput,
): Promise<FXPaymentType> {
const createdFXPayment = await this._fxPaymentsService.createFXPayment(input)
return createdFXPayment
}
#ResolveField()
intermediaries(#Parent() fxPayment: FXPaymentEntity): IntermediaryType[] {
const usedIntermediaries = [
fxPayment.nostroRequisites,
fxPayment.providerRequisites,
fxPayment.orderingRequisites,
]
return usedIntermediaries
}
}
The problem is that when mutation executed -- nested IntermediaryType contains only id field whereas other fields are nullable. I logged data in #ResolveField() and it showed me that all fields are not in null. I tried to remove #ResolveField and allow high-level resolver to resolve intermediaries field automatically (that is I just created intermediaries in my mutation) -- it doesn't work.
But still, I do really don't know what I'm doing wrong... Any attempts to forcely assign value to these fields failed also. Could you give any helpful comments on that? What should I try to do? Is everything okay with my code or not? Any ideas? Please, help.

TypeGraphql Field and Arg decorators using custom type

I'm trying to build a resolver using type-graphql library and found that I can't define custom argument type. Here is my code:
type Hits = { [K: string]: string | number }
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
#UseMiddleware(isAuthenticated)
async searchAssetList(#Arg('hits') hits: Hits) {
return [];
}
}
I got an error:
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript reflection system. You need to provide explicit type for argument named 'hits' of 'searchAssetList' of 'SearchResolver' class.
I also tried to define an input class:
type Hits = { [K: string]: string | number }
#ObjectType()
class SearchListInput {
#Field(() => GraphQLObjectType)
hits: Hits;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: SearchListInput) {
return []
}
}
and got another error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for argument named 'input' of 'searchAssetList' of 'SearchResolver' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Replacing #ObjectType with #InputType also doesn't help. How to define decorators like #Field, #Arg correctly?
Any help is appreciated. Thanks.
I think you are trying to have an Arg of an array whit numbers and strings in that case you should do
#InputType()
class HitsInput
{
#Field()
Hits: [number | string];
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
if this is not the case and you want an object whit dynamic fields you need to define the object, let say if hits have Name and Id_Hits,
#InputType()
class HitsInput
{
#Field()
Id_Hits: number;
#Field()
Name: string;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
and if you want to have a dynamic args base in the user request I am not quite sure that it is possible, but it is possible to do this
#InputType()
class HitsInput
{
[key: string]: any;
}
I had exactly the same issue, a couple of days ago, and I solve it using GraphQLScalarType by creating my own custom type
here how to do it
import { GraphQLScalarType} from "graphql";
export const GraphQLAny = new GraphQLScalarType({
name: 'Any',
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => ast
});
and in your class you can use your customTpe like this:
#ObjectType()
class SearchListInput {
#Field(() => GraphQLAny)
hits: typeof GraphQLAny;
}

NestJS transform a property using ValidationPipe before validation execution during DTO creation

I'm using the built in NestJS ValidationPipe along with class-validator and class-transformer to validate and sanitize inbound JSON body payloads. One scenario I'm facing is a mixture of upper and lower case property names in the inbound JSON objects. I'd like to rectify and map these properties to standard camel-cased models in our new TypeScript NestJS API so that I don't couple mismatched patterns in a legacy system to our new API and new standards, essentially using the #Transform in the DTOs as an isolation mechanism for the rest of the application. For example, properties on the inbound JSON object:
"propertyone",
"PROPERTYTWO",
"PropertyThree"
should map to
"propertyOne",
"propertyTwo",
"propertyThree"
I'd like to use #Transform to accomplish this, but I don't think my approach is correct. I'm wondering if I need to write a custom ValidationPipe. Here is my current approach.
Controller:
import { Body, Controller, Post, UsePipes, ValidationPipe } from '#nestjs/common';
import { TestMeRequestDto } from './testmerequest.dto';
#Controller('test')
export class TestController {
constructor() {}
#Post()
#UsePipes(new ValidationPipe({ transform: true }))
async get(#Body() testMeRequestDto: TestMeRequestDto): Promise<TestMeResponseDto> {
const response = do something useful here... ;
return response;
}
}
TestMeModel:
import { IsNotEmpty } from 'class-validator';
export class TestMeModel {
#IsNotEmpty()
someTestProperty!: string;
}
TestMeRequestDto:
import { IsNotEmpty, ValidateNested } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { TestMeModel } from './testme.model';
export class TestMeRequestDto {
#IsNotEmpty()
#Transform((propertyone) => propertyone.valueOf())
propertyOne!: string;
#IsNotEmpty()
#Transform((PROPERTYTWO) => PROPERTYTWO.valueOf())
propertyTwo!: string;
#IsNotEmpty()
#Transform((PropertyThree) => PropertyThree.valueOf())
propertyThree!: string;
#ValidateNested({ each: true })
#Type(() => TestMeModel)
simpleModel!: TestMeModel
}
Sample payload used to POST to the controller:
{
"propertyone": "test1",
"PROPERTYTWO": "test2",
"PropertyThree": "test3",
"simpleModel": { "sometestproperty": "test4" }
}
The issues I'm having:
The transforms seem to have no effect. Class validator tells me that each of those properties cannot be empty. If for example I change "propertyone" to "propertyOne" then the class validator validation is fine for that property, e.g. it sees the value. The same for the other two properties. If I camelcase them, then class validator is happy. Is this a symptom of the transform not running before the validation occurs?
This one is very weird. When I debug and evaluate the TestMeRequestDto object, I can see that the simpleModel property contains an object containing a property name "sometestproperty", even though the Class definition for TestMeModel has a camelcase "someTestProperty". Why doesn't the #Type(() => TestMeModel) respect the proper casing of that property name? The value of "test4" is present in this property, so it knows how to understand that value and assign it.
Very weird still, the #IsNotEmpty() validation for the "someTestProperty" property on the TestMeModel is not failing, e.g. it sees the "test4" value and is satisfied, even though the inbound property name in the sample JSON payload is "sometestproperty", which is all lower case.
Any insight and direction from the community would be greatly appreciated. Thanks!
You'll probably need to make use of the Advanced Usage section of the class-transformer docs. Essentially, your #Transform() would need to look something like this:
import { IsNotEmpty, ValidateNested } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { TestMeModel } from './testme.model';
export class TestMeRequestDto {
#IsNotEmpty()
#Transform((value, obj) => obj.propertyone.valueOf())
propertyOne!: string;
#IsNotEmpty()
#Transform((value, obj) => obj.PROPERTYTWO.valueOf())
propertyTwo!: string;
#IsNotEmpty()
#Transform((value, obj) => obj.PropertyThree.valueOf())
propertyThree!: string;
#ValidateNested({ each: true })
#Type(() => TestMeModel)
simpleModel!: TestMeModel
}
This should take an incoming payload of
{
"propertyone": "value1",
"PROPERTYTWO": "value2",
"PropertyThree": "value3",
}
and turn it into the DTO you envision.
Edit 12/30/2020
So the original idea I had of using #Transform() doesn't quite work as envisioned, which is a real bummer cause it looks so nice. So what you can do instead isn't quite as DRY, but it still works with class-transformer, which is a win. By making use of #Exclude() and #Expose() you're able to use property accessors as an alias for the weird named property, looking something like this:
class CorrectedDTO {
#Expose()
get propertyOne() {
return this.propertyONE;
}
#Expose()
get propertyTwo(): string {
return this.PROPERTYTWO;
}
#Expose()
get propertyThree(): string {
return this.PrOpErTyThReE;
}
#Exclude({ toPlainOnly: true })
propertyONE: string;
#Exclude({ toPlainOnly: true })
PROPERTYTWO: string;
#Exclude({ toPlainOnly: true })
PrOpErTyThReE: string;
}
Now you're able to access dto.propertyOne and get the expected property, and when you do classToPlain it will strip out the propertyONE and other properties (if you're using Nest's serialization interceptor. Otherwise in a secondary pipe you could plainToClass(NewDTO, classToPlain(value)) where NewDTO has only the corrected fields).
The other thing you may want to look into is an automapper and see if it has better capabilities for something like this.
If you're interested, here's the StackBlitz I was using to test this out
As an alternative to Jay's execellent answer, you could also create a custom pipe where you keep the logic for mapping/transforming the request payload to your desired DTO. It can be as simple as this:
export class RequestConverterPipe implements PipeTransform{
transform(body: any, metadata: ArgumentMetadata): TestMeRequestDto {
const result = new TestMeRequestDto();
// can of course contain more sophisticated mapping logic
result.propertyOne = body.propertyone;
result.propertyTwo = body.PROPERTYTWO;
result.propertyThree = body.PropertyThree;
return result;
}
export class TestMeRequestDto {
#IsNotEmpty()
propertyOne: string;
#IsNotEmpty()
propertyTwo: string;
#IsNotEmpty()
propertyThree: string;
}
You can then use it like this in your controller (but you need to make sure that the order is correct, i.e. the RequestConverterPipe must run before the ValidationPipe which also means that the ValidationPipe cannot be globally set):
#UsePipes(new RequestConverterPipe(), new ValidationPipe())
async post(#Body() requestDto: TestMeRequestDto): Promise<TestMeResponseDto> {
// ...
}

TypeGraphQL createUnionFunction with parameter

I'm trying to implement an extension of typegraphql's createUnionType() function to where I can pass in a class/ObjectType instead of hardcoding, and it will return a union type of both.
What I have so far doesn't work but I feel like it's possible. Could anyone provide any insight? Maybe it's not possible?
typedefs
import { ObjectType, Field, createUnionType, ClassType } from "type-graphql";
#ObjectType()
export default class MutationSuccess {
#Field()
success: boolean = true;
}
// Doesn't work
export const MutationResponse = (objectType: ClassType) => createUnionType({
name: 'MutationResponseType',
types: () => [MutationSuccess, objectType],
})
How I'm trying to use it in my resolver
#Resolver()
export default class RelationResolver {
#Mutation(() => MutationResponse(Relation), { description: 'follow user' })
async follow(
#Arg('relationInput') relationInput: RelationInput
): Promise<Relation | MutationSuccess> {
// do some stuff
}
}
error
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for follow
The Relation class need to be decorated with #ObjectType and the union type name has to be unique.

Object mutations in graphQL

Here is my EventAttendee Object.
const EventAttendee = new GraphQLInputObjectType({
name: 'EventAttendee',
fields: () => ({
attendeeName: {type: GraphQLString},
personalizedDateSelection: {type: new GraphQLInputObjectType()}
})
});
The personalizedDateSelection property is an dynamic one and its properties are not known now. So, In this case, I have given GraphQLInputObjectType().
But it gives an error stating EventAttendee.personalizedDateSelection field type must be Output Type.
How to define an ObjectType whose properties are not known ?
I believe you want to define an InputObjectType and not an ObjectType as stated.
With that said, what you can do is to declare personalizedDateSelection as a string for now, and then later adapt it as needed with a custom input object, once you know what it should contain. GraphQLInputObjectType is used to define new input object types, it isn't a type per se.
I wanted personalizedDateSelection property of EventAttendee to be of objectType but I dont know those properties in advance, but I am sure that it is of Object type.
So declaring this as GraphQLScalarType was the correct way to do it. Check out about GraphQLScalarType. But we need to create a custom scalar type. All scalar types can be of input type. So here is my implementation:
const PersonalizedDateSelection = new GraphQLScalarType({
name: 'PersonalizedDateSelection',
serialize: value => {
return value;
},
parseValue: value => {
return value;
},
parseLiteral: ast => {
console.log("coming in parseLiteral");
console.log(ast);
let value = {};
if (ast.kind !== Kind.OBJECT) {
throw new GraphQLError("Query error: Can only parse object but got a: " + ast.kind, [ast]);
}
ast.fields.forEach(field => {
value[field.name.value] = parseJSONLiteral(field.value);
});
return value;
}
});

Resources