6

I have been working to validate a request using the class-validator, and NestJS validation plus trying to validate the header contents.
My basic interfaces are all working, but now I am trying to compare some header field data the same way.

I had this question about the custom decorator to try to handle the headers, but the solution to that question, will return the one header. I want to be able to handle them all, similar to how all the body() data is processed.

I need to be able to create a custom decorator for extracting the header fields, and being able to pass them into the class-validator DTO.

For Instance, I want to validate three header fields, such as:

User-Agent = 'Our Client Apps'
Content-Type = 'application/json'
traceabilityId = uuid

There are more fields, but if I can get this going, then I can extrapolate out the rest. I have a simple controller example:

@Controller(/rest/package)
export class PackageController {

    constructor(
        private PackageData_:PackageService
    )
    { }

    ...

    @Post('inquiry')
    @HttpCode(HttpStatus.OK)        // Not creating data, but need body, so return 200 OK
    async StatusInquiry(
        @RequestHeader() HeaderInfo:HeadersDTO,     // This should be the Headers validation using the decorator from the question above.

I am trying to validate that the headers of the request contain some specific data, and I am using NestJS. I found this information. While this is what I want to do, and it looks proper, the ClassType reference does not exist, and I am not sure what to use instead.

From the example, the decorator is referring to.

request-header.decorator.ts

export interface iError {
    statusCode:number;
    messages:string[];
    error:string;
}

export const RequestHeader = createParamDecorator(
async (value:  any, ctx: ExecutionContext) => {

    // extract headers
    const headers = ctx.switchToHttp().getRequest().headers;

    // Convert headers to DTO object
    const dto = plainToClass(value, headers, { excludeExtraneousValues: true });

    // Validate
    const errors: ValidationError[] = await validate(dto);

    if (errors.length > 0) {
        let ErrorInfo:IError = {
            statusCode: HttpStatus.BAD_REQUEST,
            error: 'Bad Request',
            message: new Array<string>()
        };
        
        errors.map(obj => { 
            AllErrors = Object.values(obj.constraints);    
            AllErrors.forEach( (OneError) => {
            OneError.forEach( (Key) => {
                ErrorInfo.message.push(Key);
            });
        });

        // Your example, but wanted to return closer to how the body looks, for common error parsing
        //Get the errors and push to custom array
        // let validationErrors = errors.map(obj => Object.values(obj.constraints));
        throw new HttpException(`${ErrorInfo}`, HttpStatus.BAD_REQUEST);
    }

    // return header dto object
    return dto;
},

I am having trouble generically mapping the constraints into a string array.

My HeadersDTO.ts:

import { Expose } from 'class-transformer';
import { Equals, IsIn, IsString } from 'class-validator';
export class HeadersDTO {

    @IsString()
    @Equals('OurApp')
    @Expose({ name: 'user-agent' })
    public readonly 'user-agent':string;

    @IsString() 
    @IsIn(['PRODUCTION', 'TEST'])
    public readonly operationMode:string;
}

Headers being sent via Postman for the request:

Content-Type:application/json
operationMode:PRODUCTION
Accept-Language:en
5
  • I believe you followed this link - github.com/nestjs/nest/issues/4798. if yes please cross check if anything is missing Commented Apr 5, 2021 at 14:37
  • Yes, except the issue with ClassType not being found is still there, it will not compile. import { ClassType } from 'class-transformer/ClassTransformer'; Commented Apr 5, 2021 at 15:33
  • The error is Cannot find module 'class-transformer/ClassTransformer' or its corresponding type declarations.ts(2307) Commented Apr 5, 2021 at 15:40
  • 1
    I have tested below code I posted and it is working for me Commented Apr 6, 2021 at 9:42
  • Sorry, did not see you changed the ClassType<unknown> to simply be an any. Commented Apr 6, 2021 at 15:17

2 Answers 2

6

I have just tested the following code and this is working. I think you are missing appropriate type over here,

    async StatusInquiry(
        @RequestHeader() HeaderInfo:HeadersDTO,

You should have HeadersDTO passed in as param in RequestHeader Decorator this @RequestHeader(HeadersDTO) HeaderInfo:HeadersDTO,

Then I have created customDecorator.ts like this,

    export const RequestHeader = createParamDecorator(
    //Removed ClassType<unknown>,, I don't think you need this here
    async (value:  any, ctx: ExecutionContext) => {

        // extract headers
        const headers = ctx.switchToHttp().getRequest().headers;

        // Convert headers to DTO object
        const dto = plainToClass(value, headers, { excludeExtraneousValues: true });

        // Validate
        const errors: ValidationError[] = await validate(dto);
        
        if (errors.length > 0) {
            //Get the errors and push to custom array
            let validationErrors = errors.map(obj => Object.values(obj.constraints));
            throw new HttpException(`Validation failed with following Errors: ${validationErrors}`, HttpStatus.BAD_REQUEST);
        }

        // return header dto object
        return dto;
    },
);

My HeadersDTO.ts file

    export class HeadersDTO 
    {
      @IsDefined()
      @Expose({ name: 'custom-header' })
      "custom-header": string; // note the param here is in double quotes
    }

The reason i found this when I looked compiled TS file, it looks like this,

    class HeadersDTO {
    }
    tslib_1.__decorate([
        class_validator_1.IsDefined(),
        class_transformer_1.Expose({ name: 'custom-header' }),
        tslib_1.__metadata("design:type", String)
    ], HeadersDTO.prototype, "custom-header", void 0);
    exports.HeadersDTO = HeadersDTO;

I get following error when i do not pass the header ,

    [
      ValidationError {
        target: HeadersDTO { 'custom-header': undefined },
        value: undefined,
        property: 'custom-header',
        children: [],
        constraints: { isDefined: 'custom-header should not be null or undefined' }
      }
    ]
Sign up to request clarification or add additional context in comments.

10 Comments

This does give me a server error, 500 now, not the normal 400 Bad Request. Is there a way to get the default handling to kick in?
You can add your custom validation and throw exception around await validateOrReject(dto); function. I did same thing, Also note i have used validate(obj) method now. Updated answer
That did fix the issue, and I can reformat these. Was hoping to get the internal call for consistency, but can work with this.
I am not getting the header values passed in though. So while the validation is running, it is not truly validating as the header fields in quotes, are not getting populated, so their value is undefined.
I was able to see the header values i passed... I just monitored them and could see it. Are you saying you are unable to get header values ? Where are you trying to access those values?
|
3

As mentioned in the issue you referenced, you need to create a DTO class and pass it to RequestHeader decorator.

e.g


export class MyHeaderDTO {
    @IsString()
    @IsDefined()
    @Expose({ name: 'myheader1' })        // required as headers are case insensitive
    myHeader1: string;
}

...

@Get('/hello')
getHello(@RequestHeader(MyHeaderDTO) headers: MyHeaderDTO) {
    console.log(headers);
}

3 Comments

I do that, as you can see in the controller. However, I am not sending the headers and it is not failing. Also, in the request-headers.decorator.ts, that code does not run, as the ClassType is undefined. The answer that does get the headers returned, does not call the validation, which is still my main issue.
@StevenScott In your example you do @RequestHeader() HeaderInfo: HeadersDTO, it should be @RequestHeader(HeadersDTO) headers: HeadersDTO
I added the @RequestHeader(HeadersDTO) headers: HeadersDTO but that does not get the failures to occur. For instance, using Postman to post the request, I disable the User-Agent from being sent. My code has the check that it is a client of ours. I updated the question with the example DTO.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.