-
Notifications
You must be signed in to change notification settings - Fork 724
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a non-breaking change, affecting only TypeScript types, and doesn't change the implementation in any way. Motivation ========== `inversify` already has some basic support for types when binding, and retrieving bindings. However, the type support requires manual intervention from developers, and can be error-prone. For example, the following code will happily compile, even though the types here are inconsistent: ```ts container.bind<Bar>('bar').to(Bar); const foo = container.get<Foo>('bar') ``` Furthermore, this paves the way for [type-safe injection][1], which will be added once this change is in. Improved type safety ==================== This change adds an optional type parameter to the `Container`, which takes an identifier map as an argument. For example: ```ts type IdentifierMap = { foo: Foo; bar: Bar; }; const container = new Container<IdentifierMap>; ``` If a `Container` is typed like this, we now get strong typing both when binding, and getting bindings: ```ts const container = new Container<IdentifierMap>; container.bind('foo').to(Foo); // ok container.bind('foo').to(Bar); // error const foo: Foo = container.get('foo') // ok const bar: Bar = container.get('foo') // error ``` This also has the added benefit of no longer needing to pass around service identifier constants: the strings (or symbols) are all strongly typed, and will fail compilation if an incorrect one is used. Non-breaking ============ This change aims to make no breaks to the existing types, so any `Container` without an argument should continue to work as it did before. [1]: #788 (comment)
- Loading branch information
1 parent
7619dd4
commit 255acc1
Showing
8 changed files
with
384 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { interfaces } from '../../src/interfaces/interfaces'; | ||
import {Container, injectable} from '../../src/inversify'; | ||
import {expect} from 'chai'; | ||
|
||
describe('interfaces', () => { | ||
@injectable() | ||
class Foo { | ||
foo: string = ''; | ||
} | ||
|
||
@injectable() | ||
class Bar { | ||
bar: string = ''; | ||
} | ||
|
||
describe('Container', () => { | ||
describe('no binding map', () => { | ||
let container: interfaces.Container; | ||
let foo: Foo; | ||
|
||
beforeEach(() => { | ||
container = new Container(); | ||
// tslint:disable-next-line: no-unused-expression | ||
foo; | ||
}); | ||
|
||
describe('bind()', () => { | ||
it('binds without a type argument', () => { | ||
container.bind('foo').to(Foo); | ||
container.bind(Foo).to(Foo); | ||
}); | ||
|
||
it('checks bindings with an explicit type argument', () => { | ||
container.bind<Foo>('foo').to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind<Foo>('foo').to(Bar); | ||
}); | ||
|
||
it('binds a class as a service identifier', () => { | ||
container.bind(Foo).to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind(Foo).to(Bar); | ||
}); | ||
}); | ||
|
||
describe('get()', () => { | ||
beforeEach(() => { | ||
container.bind('foo').to(Foo); | ||
container.bind('bar').to(Bar); | ||
container.bind(Foo).to(Foo); | ||
container.bind(Bar).to(Bar); | ||
}); | ||
|
||
it('gets an anonymous binding', () => { | ||
foo = container.get('foo'); | ||
}); | ||
|
||
it('enforces type arguments', () => { | ||
foo = container.get<Foo>('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get<Bar>('bar'); | ||
}); | ||
|
||
it('gets a class identifier', () => { | ||
foo = container.get(Foo); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get(Bar); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('binding map', () => { | ||
let container: interfaces.Container<{foo: Foo; bar: Bar}>; | ||
let foo: Foo; | ||
|
||
beforeEach(() => { | ||
container = new Container(); | ||
// tslint:disable-next-line: no-unused-expression | ||
foo; | ||
}); | ||
|
||
describe('bind()', () => { | ||
it('enforces strict bindings', () => { | ||
container.bind('foo').to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind('foo').to(Bar); | ||
// @ts-expect-error :: unknown service identifier | ||
container.bind('unknown').to(Foo); | ||
}); | ||
}); | ||
|
||
describe('get()', () => { | ||
beforeEach(() => { | ||
container.bind('foo').to(Foo); | ||
container.bind('bar').to(Bar); | ||
}); | ||
|
||
it('enforces strict bindings', () => { | ||
foo = container.get('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get('bar'); | ||
// @ts-expect-error :: unknown service identifier | ||
expect(() => container.get('unknown')).to.throw('No matching bindings'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Strong Container typing | ||
|
||
The `Container` can take an optional type argument defining a mapping of service identifiers to types. If defined, this | ||
will add strong type-checking when declaring bindings, and when retrieving them. | ||
|
||
For example: | ||
|
||
```ts | ||
type IdentifierMap = { | ||
foo: Foo; | ||
bar: Bar; | ||
}; | ||
|
||
const container = new Container<IdentifierMap>; | ||
|
||
container.bind('foo').to(Foo); // ok | ||
container.bind('foo').to(Bar); // error | ||
|
||
const foo: Foo = container.get('foo') // ok | ||
const bar: Bar = container.get('foo') // error | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters