typescript - Crearea unei validări personalizate pentru Header cu NestJS și class-validator


Am lucrat la validarea unei cereri folosind class-validator și validarea NestJS, plus încercând să validez conținutul antetului.
Interfețele mele de bază funcționează toate, dar acum încerc să compar unele date dintr-un câmp de antet în același mod.

Am avut această întrebare despre decoratorul personalizat pentru a încerca să gestionez anteturile, dar soluția pentru acea întrebare va întoarce un singur antet. Vreau să pot să le gestionez pe toate, similar cu modul în care sunt procesate toate datele din body().

Trebuie să pot crea un decorator personalizat pentru extragerea câmpurilor din antet și să le pot transmite în DTO-ul class-validator.

De exemplu, vreau să validez trei câmpuri de antet, cum ar fi:

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

Există mai multe câmpuri, dar dacă pot să fac să meargă asta, atunci pot să extrapolez restul. Am un exemplu simplu de controler:

@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.

Încerc să validez că anteturile cererii conțin anumite date specifice și folosesc NestJS. Am găsit aceste informații. Deși asta este ceea ce vreau să fac și pare corect, referința ClassType nu există și nu sunt sigur ce să folosesc în schimb.

Din exemplu, decoratorul se referă la.

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;
},

Am probleme în a mapa în mod generic constrângerile într-un array de șiruri de caractere.

Fișierul meu 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;
}

Antetele fiind trimise prin Postman pentru cerere:

Content-Type:application/json
operationMode:PRODUCTION
Accept-Language:en

Răspuns #1

Tocmai am testat următorul cod și acesta funcționează. Cred că îți lipsește tipul corespunzător aici,

    async StatusInquiry(
        @RequestHeader() HeaderInfo:HeadersDTO,

Ar trebui să ai HeadersDTO trecut ca parametru în RequestHeader Decorator astfel @RequestHeader(HeadersDTO) HeaderInfo:HeadersDTO,

Apoi am creat customDecorator.ts astfel,

    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;
    },
);

Fișierul meu HeadersDTO.ts

    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' }
      }
    ]

Răspunsul #2

Așa cum s-a menționat în problema pe care ai referit-o, trebuie să creezi o clasă DTO și să o transmiți decoratorului RequestHeader.

de ex.

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);
}