zhuber
zhuber

Reputation: 5524

NestJS GraphQL custom argument type

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.

Upvotes: 1

Views: 3558

Answers (1)

kursus
kursus

Reputation: 1404

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.

Upvotes: 0

Related Questions