From e691632b46bae148216b9bb050297b44874f8ce7 Mon Sep 17 00:00:00 2001 From: Travis Date: Tue, 14 Apr 2020 07:39:56 +0200 Subject: [PATCH] fix(#800): Get provider info from GlobalProvider when the info is missing in injector --- .../common/src/converters/ConverterModule.ts | 6 +- .../src/converters/decorators/converter.ts | 10 ++- .../services/ConverterService.spec.ts | 31 ++++++-- .../converters/services/ConverterService.ts | 27 +++++-- .../builder/ControllerBuilder.spec.ts | 71 ++++++++++++------- packages/di/src/class/Provider.ts | 2 +- packages/di/src/services/InjectorService.ts | 12 +++- 7 files changed, 113 insertions(+), 46 deletions(-) diff --git a/packages/common/src/converters/ConverterModule.ts b/packages/common/src/converters/ConverterModule.ts index 9d7ff793f30..a40ae29d588 100644 --- a/packages/common/src/converters/ConverterModule.ts +++ b/packages/common/src/converters/ConverterModule.ts @@ -1,8 +1,8 @@ import {Module} from "@tsed/di"; -import {ArrayConverter, DateConverter, MapConverter, PrimitiveConverter, SetConverter, SymbolConverter} from "./components"; import {ConverterService} from "./services/ConverterService"; @Module({ - imports: [ArrayConverter, DateConverter, MapConverter, PrimitiveConverter, SetConverter, SymbolConverter, ConverterService] + imports: [ConverterService] }) -export class ConverterModule {} +export class ConverterModule { +} diff --git a/packages/common/src/converters/decorators/converter.ts b/packages/common/src/converters/decorators/converter.ts index 48b7da16031..4c66aaf094c 100644 --- a/packages/common/src/converters/decorators/converter.ts +++ b/packages/common/src/converters/decorators/converter.ts @@ -1,8 +1,3 @@ -/** - * @module common/converters - */ -/** */ - import {Metadata} from "@tsed/core"; import {CONVERTER} from "../constants/index"; import {registerConverter} from "../registries/ConverterRegistries"; @@ -22,7 +17,10 @@ export function Converter(...classes: any[]): Function { throw new Error("Converter decorator need at least one type like String, Date, Class, etc..."); } - registerConverter({provide: target, type: "converter"}); + registerConverter({ + provide: target, + type: "converter" + }); classes.forEach(clazz => Metadata.set(CONVERTER, target, clazz)); }; diff --git a/packages/common/src/converters/services/ConverterService.spec.ts b/packages/common/src/converters/services/ConverterService.spec.ts index 7ea34e389dc..9d9d3f7105a 100644 --- a/packages/common/src/converters/services/ConverterService.spec.ts +++ b/packages/common/src/converters/services/ConverterService.spec.ts @@ -1,10 +1,12 @@ -import {Store} from "@tsed/core"; +import {Metadata, Store} from "@tsed/core"; +import {InjectorService} from "@tsed/di"; import {inject, TestContext} from "@tsed/testing"; import {assert, expect} from "chai"; import {JsonFoo, JsonFoo1, JsonFoo2, JsonFoo3, JsonFoo4} from "../../../../../test/helper/classes"; import {ConverterService} from "../../../src/converters"; -import {PropertyDeserialize, PropertySerialize} from "../../../src/jsonschema/decorators"; +import {Default, PropertyDeserialize, PropertySerialize, PropertyType} from "../../../src/jsonschema/decorators"; import {Property} from "../../../src/jsonschema/decorators/property"; +import {CONVERTER} from "../constants"; class JsonFoo5 { @Property() @@ -335,8 +337,28 @@ describe("ConverterService", () => { ); }); }); - }); + describe("With model annotation", () => { + it("should convert model", () => { + class Model { + @Default("foo") + @PropertyType(String) + public readonly foo: string = "foo"; + } + + const injector = new InjectorService(); + injector.invoke(ConverterService); + + const converter = injector.get(ConverterService)!; + + const obj = {foo: "bar"}; + const result = converter.deserialize(obj, Model); + + expect(result).to.be.instanceof(Model); + expect(result.foo).to.equal("bar"); + }); + }); + }); describe("serialize()", () => { describe("primitive", () => { it("should convert empty string to string", () => { @@ -469,7 +491,8 @@ describe("ConverterService", () => { }); describe("isStrictModelValidation()", () => { - class Test {} + class Test { + } describe("when model is an Object", () => { it("should return false", () => { diff --git a/packages/common/src/converters/services/ConverterService.ts b/packages/common/src/converters/services/ConverterService.ts index 0aab46b62c0..18fb477f5be 100644 --- a/packages/common/src/converters/services/ConverterService.ts +++ b/packages/common/src/converters/services/ConverterService.ts @@ -1,8 +1,16 @@ import {getClass, isArrayOrArrayClass, isEmpty, isPrimitiveOrPrimitiveClass, Metadata, Store, Type} from "@tsed/core"; -import {Configuration, Injectable, InjectorService} from "@tsed/di"; +import {Configuration, GlobalProviders, Injectable, InjectorService} from "@tsed/di"; import {BadRequest} from "ts-httpexceptions"; import {PropertyMetadata} from "../../jsonschema/class/PropertyMetadata"; import {PropertyRegistry} from "../../jsonschema/registries/PropertyRegistry"; +import { + ArrayConverter, + DateConverter, + MapConverter, + PrimitiveConverter, + SetConverter, + SymbolConverter +} from "../components"; import {CONVERTER} from "../constants/index"; import {ConverterDeserializationError} from "../errors/ConverterDeserializationError"; import {ConverterSerializationError} from "../errors/ConverterSerializationError"; @@ -10,7 +18,16 @@ import {RequiredPropertyError} from "../errors/RequiredPropertyError"; import {UnknownPropertyError} from "../errors/UnknownPropertyError"; import {IConverter, IConverterOptions, IDeserializer, ISerializer} from "../interfaces/index"; -@Injectable() +@Injectable({ + imports: [ + ArrayConverter, + DateConverter, + MapConverter, + PrimitiveConverter, + SetConverter, + SymbolConverter + ] +}) export class ConverterService { private validationModelStrict = true; @@ -62,17 +79,17 @@ export class ConverterService { const serializer: ISerializer = (o: any, opt?: any) => this.serialize(o, Object.assign({}, options, opt)); if (converter && converter.serialize) { - // deserialize from a custom JsonConverter + // serialize from a custom JsonConverter return converter.serialize(obj, serializer); } if (typeof obj.serialize === "function") { - // deserialize from serialize method + // serialize from serialize method return obj.serialize(options, this); } if (typeof obj.toJSON === "function" && !obj.toJSON.$ignore) { - // deserialize from serialize method + // serialize from serialize method return obj.toJSON(); } diff --git a/packages/common/src/platform/builder/ControllerBuilder.spec.ts b/packages/common/src/platform/builder/ControllerBuilder.spec.ts index 269e8f00ac1..2b22f0c567e 100644 --- a/packages/common/src/platform/builder/ControllerBuilder.spec.ts +++ b/packages/common/src/platform/builder/ControllerBuilder.spec.ts @@ -3,7 +3,7 @@ import * as Sinon from "sinon"; import {stub} from "../../../../../test/helper/tools"; import {EndpointMetadata} from "../../mvc/models/EndpointMetadata"; import {EndpointRegistry} from "../../mvc/registries/EndpointRegistry"; -import {ControllerProvider} from "../../platform"; +import {ControllerProvider, Platform, PlatformApplication, PlatformHandler} from "../../platform"; import {SendResponseMiddleware} from "../middlewares/SendResponseMiddleware"; import {statusAndHeadersMiddleware} from "../middlewares/statusAndHeadersMiddleware"; import {PlatformDriver} from "../services/PlatformDriver"; @@ -11,7 +11,8 @@ import {PlatformRouter} from "../services/PlatformRouter"; import {ControllerBuilder} from "./ControllerBuilder"; function getControllerBuilder({propertyKey = "test", withMiddleware = true}: any = {}) { - class TestCtrl {} + class TestCtrl { + } const use = Sinon.stub(); const router = { @@ -25,22 +26,42 @@ function getControllerBuilder({propertyKey = "test", withMiddleware = true}: any stub(PlatformRouter.createRawRouter).returns(router); const injector = new InjectorService(); + + injector.addProvider(PlatformRouter, { + useClass: PlatformRouter + }); + injector.addProvider(PlatformHandler, { + useClass: PlatformHandler + }); + injector.addProvider(PlatformApplication, { + useClass: PlatformApplication + }); + injector.addProvider(Platform, { + useClass: Platform + }); + const provider = new ControllerProvider(TestCtrl); if (withMiddleware) { provider.middlewares = { - use: [function controllerUse() {}], - useAfter: [function controllerAfter() {}], - useBefore: [function controllerBefore() {}] + use: [function controllerUse() { + }], + useAfter: [function controllerAfter() { + }], + useBefore: [function controllerBefore() { + }] }; } const endpoint = new EndpointMetadata({target: TestCtrl, propertyKey}); if (withMiddleware) { - endpoint.before([function endpointBefore() {}]); - endpoint.after([function endpointAfter() {}]); - endpoint.middlewares = [function endpointUse() {}]; + endpoint.before([function endpointBefore() { + }]); + endpoint.after([function endpointAfter() { + }]); + endpoint.middlewares = [function endpointUse() { + }]; } // @ts-ignore @@ -116,15 +137,15 @@ describe("ControllerBuilder", () => { router.use .getCall(1) .should.have.been.calledWithExactly( - "/", - Sinon.match.func, - provider.middlewares.use[0], - endpoint.beforeMiddlewares[0], - endpoint.middlewares[0], - endpoint, - Sinon.match.func, - endpoint.afterMiddlewares[0] - ); + "/", + Sinon.match.func, + provider.middlewares.use[0], + endpoint.beforeMiddlewares[0], + endpoint.middlewares[0], + endpoint, + Sinon.match.func, + endpoint.afterMiddlewares[0] + ); router.use.getCall(2).should.have.been.calledWithExactly(provider.middlewares.useAfter[0]); // controller }); @@ -144,14 +165,14 @@ describe("ControllerBuilder", () => { router.use .getCall(1) .should.have.been.calledWithExactly( - Sinon.match.func, - provider.middlewares.use[0], - endpoint.beforeMiddlewares[0], - endpoint.middlewares[0], - endpoint, - Sinon.match.func, - endpoint.afterMiddlewares[0] - ); + Sinon.match.func, + provider.middlewares.use[0], + endpoint.beforeMiddlewares[0], + endpoint.middlewares[0], + endpoint, + Sinon.match.func, + endpoint.afterMiddlewares[0] + ); router.use.getCall(2).should.have.been.calledWithExactly(provider.middlewares.useAfter[0]); // controller }); diff --git a/packages/di/src/class/Provider.ts b/packages/di/src/class/Provider.ts index 2fa7d1565e8..238d5e613f2 100644 --- a/packages/di/src/class/Provider.ts +++ b/packages/di/src/class/Provider.ts @@ -4,7 +4,7 @@ import {IProvider} from "../interfaces/IProvider"; import {ProviderType} from "../interfaces/ProviderType"; import {TokenProvider} from "../interfaces/TokenProvider"; -export class Provider implements IProvider { +export class Provider implements IProvider { @Enumerable() public root: boolean = false; diff --git a/packages/di/src/services/InjectorService.ts b/packages/di/src/services/InjectorService.ts index 602407c79cf..c4612a9277d 100644 --- a/packages/di/src/services/InjectorService.ts +++ b/packages/di/src/services/InjectorService.ts @@ -101,7 +101,7 @@ export class InjectorService extends Container { * @param token * @param instance */ - public forkProvider(token: TokenProvider, instance?: any): Provider { + public forkProvider(token: TokenProvider, instance?: any): Provider { const provider = this.addProvider(token).getProvider(token)!; if (!instance) { @@ -178,7 +178,7 @@ export class InjectorService extends Container { locals: Map = new LocalsContainer(), options: Partial> = {} ): T { - const provider = this.getProvider(token); + const provider = this.ensureProvider(token); let instance: any; !locals.has(Configuration) && locals.set(Configuration, this.settings); @@ -474,6 +474,14 @@ export class InjectorService extends Container { }; } + protected ensureProvider(token: TokenProvider): Provider | undefined { + if (!this.hasProvider(token) && GlobalProviders.has(token)) { + this.addProvider(token); + } + + return this.getProvider(token)!; + } + /** * Invoke a class method and inject service. *