Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(di): preserve original metadata on @Inject decorator #2400

Merged
merged 2 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/di/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
coverageThreshold: {
global: {
statements: 98.81,
branches: 96.71,
branches: 96.54,
lines: 98.81,
functions: 98.32
}
Expand Down
4 changes: 2 additions & 2 deletions packages/di/src/common/decorators/inject.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe("@Inject()", () => {
class MyService1 implements InterfaceGroup {
readonly type: string = "service1";

constructor(@Inject(InjectorService) readonly injector: InjectorService) {}
constructor(@Inject(InjectorService) readonly injector: any) {}
}

@Injectable({
Expand All @@ -76,7 +76,7 @@ describe("@Inject()", () => {
class MyService2 implements InterfaceGroup {
readonly type: string = "service2";

constructor(@Inject(InjectorService) readonly injector: InjectorService) {}
constructor(@Inject(InjectorService) readonly injector: any) {}
}

const TokenAsync = Symbol.for("MyService2");
Expand Down
6 changes: 3 additions & 3 deletions packages/di/src/common/decorators/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {DI_PARAM_OPTIONS, INJECTABLE_PROP} from "../constants/constants";
import {InvalidPropertyTokenError} from "../errors/InvalidPropertyTokenError";
import type {InjectablePropertyOptions} from "../interfaces/InjectableProperties";
import {TokenProvider} from "../interfaces/TokenProvider";
import {getConstructorDependencies, setConstructorDependencies} from "../utils/getConstructorDependencies";

export function injectProperty(target: any, propertyKey: string, options: Partial<InjectablePropertyOptions>) {
Store.from(target).merge(INJECTABLE_PROP, {
Expand Down Expand Up @@ -39,18 +40,17 @@ export function Inject(token?: TokenProvider | (() => TokenProvider), onGet = (b
switch (bindingType) {
case DecoratorTypes.PARAM_CTOR:
if (token) {
const paramTypes = Metadata.getParamTypes(target, propertyKey);
const paramTypes = getConstructorDependencies(target);
const type = paramTypes[descriptor as number];

paramTypes[descriptor as number] = type === Array ? [token] : token;

Metadata.setParamTypes(target, propertyKey!, paramTypes);
setConstructorDependencies(target, paramTypes);
}
break;

case DecoratorTypes.PROP:
const useType = token || Metadata.getType(target, propertyKey);
const originalType = Metadata.getType(target, propertyKey);

if (useType === Object) {
throw new InvalidPropertyTokenError(target, String(propertyKey));
Expand Down
2 changes: 1 addition & 1 deletion packages/di/src/common/domain/Provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {classOf, getClassOrSymbol, isClass, methodsOf, nameOf, Store, Type} from "@tsed/core";
import {classOf, getClassOrSymbol, isClass, Metadata, methodsOf, nameOf, Store, Type} from "@tsed/core";
import {ProviderOpts} from "../interfaces/ProviderOpts";
import {TokenProvider} from "../interfaces/TokenProvider";
import {ProviderScope} from "./ProviderScope";
Expand Down
1 change: 1 addition & 0 deletions packages/di/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ export * from "./services/InjectorService";
export * from "./utils/colors";
export * from "./utils/createContainer";
export * from "./utils/getConfiguration";
export * from "./utils/getConstructorDependencies";
export * from "./utils/resolveControllers";
3 changes: 2 additions & 1 deletion packages/di/src/common/services/InjectorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {ResolvedInvokeOptions} from "../interfaces/ResolvedInvokeOptions";
import {TokenProvider} from "../interfaces/TokenProvider";
import {GlobalProviders} from "../registries/GlobalProviders";
import {createContainer} from "../utils/createContainer";
import {getConstructorDependencies} from "../utils/getConstructorDependencies";
import {resolveControllers} from "../utils/resolveControllers";
import {DIConfiguration} from "./DIConfiguration";

Expand Down Expand Up @@ -701,7 +702,7 @@ export class InjectorService extends Container {
};
} else {
// useClass
deps = deps || Metadata.getParamTypes(provider.useClass);
deps = deps || getConstructorDependencies(provider.useClass);
construct = (deps: TokenProvider[]) => new provider.useClass(...deps);
}

Expand Down
9 changes: 9 additions & 0 deletions packages/di/src/common/utils/getConstructorDependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Metadata} from "@tsed/core";

export function getConstructorDependencies(target: any) {
return [...(Metadata.get("override:ctor:design:paramtypes", target, undefined) || Metadata.getParamTypes(target) || [])];
}

export function setConstructorDependencies(target: any, deps: any[]) {
Metadata.set("override:ctor:design:paramtypes", deps, target);
}
6 changes: 3 additions & 3 deletions packages/specs/schema/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ module.exports = {
roots: ["<rootDir>/src", "<rootDir>/test"],
coverageThreshold: {
global: {
statements: 99.45,
branches: 96.16,
statements: 99.46,
branches: 96.23,
functions: 100,
lines: 99.45
lines: 99.46
}
},
moduleNameMapper: {
Expand Down
7 changes: 4 additions & 3 deletions packages/specs/schema/src/components/anyMapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {JsonLazyRef} from "../domain/JsonLazyRef";
import {JsonSchema} from "../domain/JsonSchema";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {execMapper, oneOfMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {mapGenericsOptions} from "../utils/generics";
import {toRef} from "../utils/ref";

Expand All @@ -20,8 +20,9 @@ export function anyMapper(input: any, options: JsonSchemaOptions = {}): any {
return toRef(enumSchema, enumSchema.toJSON(options), options);
}

if ("toJSON" in input) {
const schema = input.toJSON(mapGenericsOptions(options));
if (input.kind) {
const kind = oneOfMapper(input.kind, "map");
const schema = execMapper(kind, input, mapGenericsOptions(options));

return input.canRef ? toRef(input, schema, options) : schema;
}
Expand Down
7 changes: 6 additions & 1 deletion packages/specs/schema/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ export * from "./mapMapper";
export * from "./objectMapper";
export * from "./ofMapper";
export * from "./operationInFilesMapper";
export * from "./operationInParameterMapper";
export * from "./operationInParametersMapper";
export * from "./operationInQueryMapper";
export * from "./operationParameterMapper";
export * from "./operationMapper";
export * from "./operationMediaMapper";
export * from "./operationRequestBodyMapper";
export * from "./operationResponseMapper";
export * from "./pathsMapper";
export * from "./propertiesMapper";
export * from "./schemaMapper";
16 changes: 8 additions & 8 deletions packages/specs/schema/src/components/lazyRefMapper.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {JsonLazyRef} from "../domain/JsonLazyRef";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {mapGenericsOptions} from "../utils/generics";
import {createRef, toRef} from "../utils/ref";

export function lazyRefMapper(input: JsonLazyRef, options: JsonSchemaOptions) {
const name = input.name;
export function lazyRefMapper(jsonLazyRef: JsonLazyRef, options: JsonSchemaOptions) {
const name = jsonLazyRef.name;

if (options.$refs?.find((t: any) => t === input.target)) {
return createRef(name, input.schema, options);
if (options.$refs?.find((t: any) => t === jsonLazyRef.target)) {
return createRef(name, jsonLazyRef.schema, options);
}

options.$refs = [...(options.$refs || []), input.target];
options.$refs = [...(options.$refs || []), jsonLazyRef.target];

const schema = input.toJSON(mapGenericsOptions(options));
const schema = jsonLazyRef.getType() && execMapper("schema", jsonLazyRef.schema, mapGenericsOptions(options));

return toRef(input.schema, schema, options);
return toRef(jsonLazyRef.schema, schema, options);
}

registerJsonSchemaMapper("lazyRef", lazyRefMapper);
2 changes: 1 addition & 1 deletion packages/specs/schema/src/components/mapMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapp
* @param options
* @ignore
*/
export function mapMapper(input: Map<string, any>, {ignore = [], ...options}: JsonSchemaOptions): any {
export function mapMapper(input: Map<string, any>, {ignore = [], ...options}: JsonSchemaOptions = {}): any {
options = mapGenericsOptions(options);

return Array.from(input.entries()).reduce((obj: any, [key, value]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {cleanObject} from "@tsed/core";
import {registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import type {JsonParameterOptions} from "./operationParameterMapper";
import type {JsonParameterOptions} from "./operationInParameterMapper";

export function operationInFilesMapper(parameter: any, {jsonSchema}: JsonParameterOptions) {
const schema = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {OS3Schema} from "@tsed/openspec";
import {camelCase} from "change-case";
import type {JSONSchema6} from "json-schema";
import {JsonParameter} from "../domain/JsonParameter";
import {isParameterType, JsonParameterTypes} from "../domain/JsonParameterTypes";
import {JsonParameterTypes} from "../domain/JsonParameterTypes";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, hasMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {popGenerics} from "../utils/generics";
Expand All @@ -21,11 +21,7 @@ function mapOptions(parameter: JsonParameter, options: JsonSchemaOptions = {}) {
};
}

export function operationParameterMapper(jsonParameter: JsonParameter, opts?: JsonSchemaOptions) {
if (!isParameterType(jsonParameter.get("in"))) {
return null;
}

export function operationInParameterMapper(jsonParameter: JsonParameter, opts?: JsonSchemaOptions) {
const options = mapOptions(jsonParameter, opts);
const schemas = {...(options.schemas || {})};

Expand Down Expand Up @@ -56,4 +52,4 @@ export function operationParameterMapper(jsonParameter: JsonParameter, opts?: Js
return parameter;
}

registerJsonSchemaMapper("operationParameter", operationParameterMapper);
registerJsonSchemaMapper("operationInParameter", operationInParameterMapper);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {JsonParameter} from "../domain/JsonParameter";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

export function operationInParametersMapper(parameters: JsonParameter[], options: JsonSchemaOptions) {
return parameters.flatMap((parameter) => execMapper("operationInParameter", parameter, options)).filter(Boolean);
}

registerJsonSchemaMapper("operationInParameters", operationInParametersMapper);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {cleanObject} from "@tsed/core";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";
import {createRefName} from "../utils/ref";
import type {JsonParameterOptions} from "./operationParameterMapper";
import type {JsonParameterOptions} from "./operationInParameterMapper";

function inlineReference(parameter: any, {jsonParameter, ...options}: JsonSchemaOptions) {
const name = createRefName(jsonParameter.$schema.getName(), options);
Expand Down
65 changes: 65 additions & 0 deletions packages/specs/schema/src/components/operationMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {getStatusMessage} from "../constants/httpStatusMessages";
import {JsonOperation} from "../domain/JsonOperation";
import {JsonParameter} from "../domain/JsonParameter";
import {isParameterType, JsonParameterTypes} from "../domain/JsonParameterTypes";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

function extractParameters(jsonOperation: JsonOperation, options: JsonSchemaOptions) {
return jsonOperation
.get("parameters")
.filter((parameter: JsonParameter) => isParameterType(parameter.get("in")))
.reduce(
(inputs: [any[], JsonParameter[]], parameter: JsonParameter) => {
const [parameters, bodyParameters] = inputs;

if ([JsonParameterTypes.BODY, JsonParameterTypes.FILES].includes(parameter.get("in"))) {
return [parameters, [...bodyParameters, parameter]];
}

return [[...parameters, parameter], bodyParameters];
},
[[], []]
);
}

export function operationMapper(jsonOperation: JsonOperation, {tags = [], defaultTags = [], ...options}: JsonSchemaOptions = {}) {
const {consumes, produces, ...operation} = execMapper("map", jsonOperation, {...options, ignore: ["parameters"]});

if (operation.security) {
operation.security = [].concat(operation.security);
}

if (jsonOperation.get("responses").size === 0) {
operation.responses = {
"200": {
description: getStatusMessage(200)
}
};
}

const parametersOptions = {
...options,
consumes: jsonOperation.get("consumes")?.length ? jsonOperation.get("consumes") : ["application/json"]
};

const [parameters, bodyParameters] = extractParameters(jsonOperation, parametersOptions);

operation.parameters = execMapper("operationInParameters", parameters, options);

if (bodyParameters.length) {
operation.requestBody = execMapper("operationRequestBody", bodyParameters, parametersOptions);
}

const operationTags = operation.tags?.length ? operation.tags : defaultTags;

if (operationTags.length) {
operation.tags = operationTags.map(({name}: any) => name);
}

tags.push(...operationTags);

return operation;
}

registerJsonSchemaMapper("operation", operationMapper);
11 changes: 11 additions & 0 deletions packages/specs/schema/src/components/operationMediaMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {JsonMedia} from "../domain/JsonMedia";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

export function operationMediaMapper(jsonMedia: JsonMedia, options: JsonSchemaOptions) {
let groups = [...(jsonMedia.groups || [])];

return execMapper("map", jsonMedia, {...options, groups, groupsName: jsonMedia.groupsName});
}

registerJsonSchemaMapper("operationMedia", operationMediaMapper);
73 changes: 73 additions & 0 deletions packages/specs/schema/src/components/operationRequestBodyMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {JsonOperation} from "../domain/JsonOperation";
import {JsonParameter} from "../domain/JsonParameter";
import {isParameterType, JsonParameterTypes} from "../domain/JsonParameterTypes";
import {JsonRequestBody} from "../domain/JsonRequestBody";
import {JsonSchema} from "../domain/JsonSchema";
import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions";
import {execMapper, registerJsonSchemaMapper} from "../registries/JsonSchemaMapperContainer";

function buildSchemaFromBodyParameters(parameters: JsonParameter[], options: JsonSchemaOptions) {
let schema = new JsonSchema();
const props: any = {};
const refs: JsonSchema[] = [];
let propsLength = 0;

parameters.forEach((parameter) => {
const name = parameter.getName();

[...parameter.entries()]
.filter(([key]) => !["in", "name"].includes(key))
.forEach(([key, value]) => {
if (props[key] === undefined) {
props[key] = value;
}
});

const jsonParameter = execMapper("operationInParameter", parameter, options);

if (name) {
schema.addProperty(
name,
jsonParameter.schema || {
type: jsonParameter.type
}
);

if (parameter.get("required")) {
schema.addRequired(name);
}

propsLength++;
} else {
refs.push(jsonParameter);
}
});

if (!propsLength) {
if (refs.length === 1) {
return refs[0];
}
}

schema.type("object");

return {
schema: schema.toJSON(options),
required: false,
...props
};
}

export function operationRequestBodyMapper(bodyParameters: JsonParameter[], {consumes, ...options}: JsonSchemaOptions) {
const {schema, examples, in: _, ...props} = buildSchemaFromBodyParameters(bodyParameters, options);

const requestBody = new JsonRequestBody(props);

consumes.forEach((consume: string) => {
requestBody.addContent(consume, schema, examples);
});

return execMapper("map", requestBody, options);
}

registerJsonSchemaMapper("operationRequestBody", operationRequestBodyMapper);
Loading
Loading