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

feature(core): Add multi option for custom providers #1517

Closed
Closed
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
45 changes: 45 additions & 0 deletions integration/injector/e2e/multi-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { MultiProviderUseValueModule } from '../src/multi-provider/multi-provider-use-value.module';
import { MultiProviderUseFactoryModule } from '../src/multi-provider/multi-provider-use-factory.module';
import { MultiProviderUseClassModule } from '../src/multi-provider/multi-provider-use-class.module';
import { MultiProviderMixedModule } from '../src/multi-provider/multi-provider-mixed.module';
import { MixedMultiProviderException } from '@nestjs/core/errors/exceptions/mixed-multi-provider.exception';

describe('MultiProvider', () => {
it(`should return an array of values when using the "multi" option and "useValue"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseValueModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b']);
});

it(`should return an array of values when using the "multi" option and "useFactory"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseFactoryModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b']);
});

it(`should return an array of values when using the "multi" option and "useClass"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseClassModule],
});
const app = await builder.compile();
expect(app.get('TEST')[0].test()).to.deep.eq('a');
expect(app.get('TEST')[1].test()).to.deep.eq('b');
});

it(`should throw an error if "mutli" value is mixed with the same token`, async () => {
try {
const builder = Test.createTestingModule({
imports: [MultiProviderMixedModule],
});
await builder.compile();
} catch (err) {
expect(err).to.be.instanceof(MixedMultiProviderException);
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Module, Inject } from '@nestjs/common';

@Module({
providers: [

{
provide: 'TEST',
useValue: 'a',
multi: true,
},
{
provide: 'TEST',
useValue: 'b',
multi: false,
}],
})
export class MultiProviderMixedModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Module, Inject } from '@nestjs/common';

class Test1 {
test() {
return 'a';
}
}
class Test2 {
constructor(@Inject('B_VALUE') private b: string) { }
test() {
return this.b;
}
}

@Module({
providers: [
{
provide: 'B_VALUE',
useValue: 'b',
},
{
provide: 'TEST',
useClass: Test1,
multi: true,
},
{
provide: 'TEST',
useClass: Test2,
multi: true,
}],
})
export class MultiProviderUseClassModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';

@Module({
providers: [{
provide: 'TEST',
useFactory: () => 'a',
multi: true,
},
{
provide: 'B_VALUE',
useValue: 'b',
},
{
provide: 'TEST',
useFactory: (b) => b,
inject: ['B_VALUE'],
multi: true,
}],
})
export class MultiProviderUseFactoryModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';

@Module({
providers: [{
provide: 'TEST',
useValue: 'a',
multi: true,
},
{
provide: 'TEST',
useValue: 'b',
multi: true,
}],
})
export class MultiProviderUseValueModule { }
15 changes: 15 additions & 0 deletions packages/common/interfaces/modules/provider.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,31 @@ export interface ClassProvider {
provide: any;
useClass: Type<any>;
scope?: Scope;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}

export interface ValueProvider {
provide: any;
useValue: any;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}

export interface FactoryProvider {
provide: any;
useFactory: (...args: any[]) => any;
inject?: Array<Type<any> | string | any>;
scope?: Scope;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}
31 changes: 31 additions & 0 deletions packages/core/errors/exceptions/mixed-multi-provider.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MIXED_MULTI_PROVIDER_MESSAGE } from '../messages';
import { RuntimeException } from './runtime.exception';

/**
* Errors which should get thrown when the user
* mixes up the `multi` option of a provider
*
* ```typescript
* // Will throw an exception
* @Module({
* providers: [
* {
* useValue: 'eng',
* provide: 'LANG',
* multi: true,
* },
* {
* useValue: 'de',
* provide: 'LANG',
* // Not allowed, because inconsistent value for `multi`
* multi: false,
* },
* ],
* })
* ```
*/
export class MixedMultiProviderException extends RuntimeException {
constructor(name: string) {
super(MIXED_MULTI_PROVIDER_MESSAGE(name));
}
}
4 changes: 4 additions & 0 deletions packages/core/errors/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export const UNKNOWN_DEPENDENCIES_MESSAGE = (
return message;
};

export const MIXED_MULTI_PROVIDER_MESSAGE = (
name: string
) => `Mixed multi-provider for ${name}.`;

export const INVALID_MIDDLEWARE_MESSAGE = (
text: TemplateStringsArray,
name: string,
Expand Down
1 change: 1 addition & 0 deletions packages/core/injector/instance-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class InstanceWrapper<T = any> {
public readonly inject?: (string | symbol | Function | Type<any>)[];
public readonly async?: boolean;
public readonly host?: Module;
public readonly multi?: boolean | undefined;
public readonly scope?: Scope = Scope.DEFAULT;
public forwardRef?: boolean;

Expand Down
Loading