5

I'm trying to correctly define OpenAPI spec for the purposes of generating api client from that spec. I've encoutered a problem where we have a complex query object with nested objects and arrays of objects for get a GET route.

Lets take these classes as an example.

class Person {
  @ApiProperty()
  name!: string
  @ApiProperty()
  location!: string
}

class CompanyDto {
  @ApiProperty()
  name!: string

  @ApiProperty({
    type: [Person],
  })
  employees!: Person[]
}

And a get request with @Query decorator.

  @Get('test')
  async testing(@Query() dto: CompanyDto): Promise<void> {
    // ...
  }

What I'm getting is.

    {
      get: {
        operationId: 'testing',
        parameters: [
          {
            name: 'name',
            required: true,
            in: 'query',
            schema: {
              type: 'string',
            },
          },
          {
            name: 'name',
            in: 'query',
            required: true,
            schema: {
              type: 'string',
            },
          },
          {
            name: 'location',
            in: 'query',
            required: true,
            schema: {
              type: 'string',
            },
          },
        ],
        responses: {
          '200': {
            description: '',
          },
        },
        tags: ['booking'],
      },
    }

I've also tries to define Query params by adding @ApiQuery decorator and it almost works.

  @ApiQuery({
    style: 'deepObject',
    type: CompanyDto,
  })

--

{
  get: {
    operationId: 'testing',
    parameters: [
      {
        name: 'name',
        required: true,
        in: 'query',
        schema: {
          type: 'string',
        },
      },
      {
        name: 'name',
        in: 'query',
        required: true,
        schema: {
          type: 'string',
        },
      },
      {
        name: 'location',
        in: 'query',
        required: true,
        schema: {
          type: 'string',
        },
      },
      {
        name: 'name',
        in: 'query',
        required: true,
        schema: {
          type: 'string',
        },
      },
      {
        name: 'employees',
        in: 'query',
        required: true,
        schema: {
          type: 'array',
          items: {
            $ref: '#/components/schemas/Person',
          },
        },
      },
    ],
    responses: {
      '200': {
        description: '',
      },
    },
    tags: ['booking'],
  },
}

However now I'm getting duplicate query definitions mashed in to one. Is there a way to prevent or overwrite @Query definition? Or just a better way to define complex @Query in general?

3
  • OpenAPI does not support nested objects in query parameters. Send nested objects in the POST request body instead. Commented Feb 7, 2022 at 9:35
  • Thanks, I'll use POST for requests with nested objects. However I would still like to use GET for non-nested objects. Is there any way to overwrite or disable spec generation from @Query decorator to prevent duplicate parameter definition? Commented Feb 7, 2022 at 10:47
  • I'm not famliar with NestJs. Maybe someone else here knows. Commented Feb 7, 2022 at 11:49

2 Answers 2

0

Ended up creating a custom decorator to extract query without generating OpenAPI Spec.

export const SilentQuery = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    if (data) {
      return request.query[data]
    } else {
      return request.query
    }
  },
)

So now you can use @ApiQuery with deepObject style.

Also if your're using ValidationPipes with class-validator for example. Make sure to set validateCustomDecorators to true

@SilentQuery(new ValidationPipe({ validateCustomDecorators: true }))
Sign up to request clarification or add additional context in comments.

Comments

0

I forked this answer it worked great but I was wondering how I could do it better. Now it works like a native query but only with support for nested objects

import {
  createParamDecorator,
  ExecutionContext,
} from '@nestjs/common';
import { DECORATORS} from "@nestjs/swagger/dist/constants"


export const CustomQuery = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    if (data) {
      return request.query[data]
    } else {
      return request.query
    }
  }, [
    (target, key: string, index) => {
      const queryParamsType = Reflect.getMetadata('design:paramtypes', target, key);

      Reflect.defineMetadata(
        DECORATORS.API_PARAMETERS,
        [
          {
            in: "query",
            type: queryParamsType[index],
            style: "deepObject",
            required: false,
            description: `query parameters ${key} controller`
          }
        ],
        target[key]
      );

      return target[key];
    }
  ]
)

how do use it:

  @ApiOperation({ summary: "Получить все карты" })
  @Get('/')
  async getAll(@CustomQuery() query: QueryCardsDto): Promise<PageDto<CardModel> | CardModel[]> {
    return this.cardService.getAllCards(query)
  }

for work ValidationPipes for CustomQuery im did next in file main.ts:

 app.useGlobalPipes(
    new ValidationPipe({
      validateCustomDecorators: true, // <---- added this
      transform: true,
      whitelist: true,
      exceptionFactory(errors: ValidationError[]) {
        return new UnprocessableEntityException(
          errors,
          "The given data was invalid."
        );
      }
    })
  );

Comments

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.