UNPKG

@tsed/schema

Version:
157 lines (156 loc) 5.45 kB
import { __decorate, __metadata } from "tslib"; import { ancestorsOf, DecoratorTypes, isClass, isMethodDescriptor, Metadata, prototypeOf } from "@tsed/core"; import { JsonEntityComponent } from "../decorators/config/jsonEntityComponent.js"; import { JsonEntityStore } from "./JsonEntityStore.js"; import { JsonParameter } from "./JsonParameter.js"; /** * Store for method parameter metadata and schema information. * * JsonParameterStore manages metadata for method parameters decorated with decorators * like `@BodyParams()`, `@PathParams()`, `@QueryParams()`, etc. It handles parameter * validation, transformation pipes, and OpenAPI parameter generation. * * ### Responsibilities * * - **Parameter Definition**: Maintains JsonParameter for OpenAPI spec generation * - **Type Resolution**: Resolves parameter types from TypeScript metadata * - **Validation**: Manages required/optional state and validation rules * - **Transformation**: Coordinates pipe execution for value transformation * - **Location Tracking**: Stores where the parameter comes from (path, query, body, etc.) * * ### Usage * * ```typescript * // Get parameter store * const paramStore = JsonParameterStore.get(MyController, "myMethod", 0); * * // Access parameter definition * const parameter = paramStore.parameter; * * // Check if required * const isRequired = paramStore.required; * * // Get all parameters for a method * const params = JsonParameterStore.getParams(MyController, "myMethod"); * ``` * * ### Parameter Types * * Parameters can come from different locations: * - **path**: URL path parameters (`/users/:id`) * - **query**: Query string parameters (`?page=1`) * - **body**: Request body * - **header**: HTTP headers * - **cookies**: Cookie values * - **files**: File uploads * * ### Validation * * The store provides validation helpers: * - `required`: Marks parameter as required * - `allowedRequiredValues`: Values that bypass required check * - `isRequired(value)`: Checks if a value satisfies required constraint * * ### Transformation Pipes * * Parameters can have transformation pipes that process values: * ```typescript * @Controller("/") * class MyController { * @Get("/:id") * get(@PathParams("id") @UsePipe(ParseIntPipe) id: number) { * // id is automatically transformed to number * } * } * ``` * * ### Expression Binding * * Parameters can use expressions to extract nested values: * ```typescript * @BodyParams("user.address.city") city: string * // Extracts: request.body.user.address.city * ``` * * @public */ let JsonParameterStore = class JsonParameterStore extends JsonEntityStore { constructor(options) { super(options); /** * Ref to JsonParameter when the decorated object is a parameter. */ this.parameter = new JsonParameter(); this.parent = JsonEntityStore.fromMethod(this.target, this.propertyKey); this.pipes = options.pipes || []; this.paramType = options.paramType || this.paramType; this.expression = options.expression || this.expression; this.dataPath = options.dataPath || this.dataPath; } /** * Return the required state. * @returns {boolean} */ get required() { return !!this.parameter.get("required"); } set required(value) { this.parameter.required(value); } get allowedRequiredValues() { return this.schema.getAllowedRequiredValues(); } get schema() { return this.parameter.schema(); } static getParams(target, propertyKey) { const params = []; const klass = ancestorsOf(target) .reverse() .find((target) => { return isMethodDescriptor(target, propertyKey) && JsonEntityStore.fromMethod(target, propertyKey).children.size; }); if (klass) { JsonEntityStore.fromMethod(klass, propertyKey).children.forEach((param, index) => { params[+index] = param; }); return params; } return []; } static get(target, propertyKey, index) { return JsonEntityStore.from(prototypeOf(target), propertyKey, index); } /** * Check precondition between value, required and allowedRequiredValues to know if the entity is required. * @param value * @returns {boolean} */ isRequired(value) { return this.required && [undefined, null, ""].includes(value) && !this.allowedRequiredValues.includes(value); } build() { if (!this._type) { const type = Metadata.getParamTypes(prototypeOf(this.target), this.propertyKey)[this.index]; this.buildType(type); } this._type = this._type || Object; this.parent.children.set(this.index, this); this.parent.operation.addParameter(this.index, this.parameter); if (this.collectionType) { this.parameter.schema().type(this.collectionType); } if (isClass(this._type)) { this.parameter.schema().itemSchema(this._type); } else { this.parameter.itemSchema().type(this._type); } } }; JsonParameterStore = __decorate([ JsonEntityComponent(DecoratorTypes.PARAM), __metadata("design:paramtypes", [Object]) ], JsonParameterStore); export { JsonParameterStore }; export const ParamMetadata = JsonParameterStore;