Skip to content

Proposal: generic @Param() decorators #291

Closed
@marshall007

Description

@marshall007

This spawned out of the discussion following #234 (comment). Pending the removal of the existing @Param(s) decorators in #289. I would like to propose that we repurpose them as generic decorators that allow you to populate from arbitrary request options.

enum ParamType {
  Params = 'params',
  Query = 'query',
  Body = 'body',
  Header = 'headers',
  // ...
}

interface ParamOptions {
  required?: boolean = true,
  allow?: ParamType | ParamType[] | { [key: ParamType]: string | string[] | boolean },
}

function Param(options: ParamOptions);
function Params(type: Type, validate: boolean | ValidatorOptions = true)

Parameter Usage

@JsonController()
class ProductsController {

  @Get("/products")
  @Get("/categories/:categoryId/products")
  getProducts(
    @Param({
      required: false,
      allow: ParamType.Query,
    })
    tagId: number, // req.query.tagId

    @Param({
      // required: true,
      allow: [ ParamType.Path, ParamType.Query ],
    })
    categoryId: number, // req.params.categoryId || req.query.categoryId

    @Param({
      // required: true,
      allow: {
        [ParamType.Query]: [ 'api_key', 'apiKey' ],
        [ParamType.Header]: 'X-Auth-Api-Key',
      }
    })
    apiKey: string, // req.query.api_key || req.query.apiKey || req.header('x-auth-api-key')
  ) { /* ... */ }

}

Class-Based Usage

We can start by splitting out our authentication/session related parameters as it's likely that many services will require them:

class AuthParams {
  @Param({
    required: false,
    allow: ParamType.Session,
  })
  user: UserSession;

  @Param({
    allow: {
      [ParamType.Query]: [ 'api_key', 'apiKey' ],
      [ParamType.Header]: 'X-Auth-Api-Key',
    }
  })
  apiKey: string;
}

Next create a class to encapsulate your request definition (ideally these classes could support all class-validator decorators as well):

class GetProductsByCategory {
  @Param({
    required: false,
    allow: ParamType.Query,
  })
  tagId: number;

  @Param({
    allow: [ ParamType.Path, ParamType.Query ],
  })
  categoryId: number;

  @Params() // could support nesting?
  auth: AuthParams;
}

Now we can inject shared, class-based request definitions into our controller methods:

@JsonController()
class ProductsController {
  @Get("/products")
  @Get("/categories/:categoryId/products")
  getProducts(
    @Params(GetProductsByCategory) req: GetProductsByCategory,
    @Params(AuthParams) auth: AuthParams,
  ) {  }
}

Metadata

Metadata

Assignees

Labels

type: discussionIssues discussing any topic.type: featureIssues related to new features.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions