Skip to content

Commit b6ce426

Browse files
Raymond Fengraymondfeng
authored andcommitted
feat(context): Add toJSON() for Context & Binding
Provide customization to serialize Context and Binding objects to JSON
1 parent 1499eb5 commit b6ce426

File tree

5 files changed

+170
-13
lines changed

5 files changed

+170
-13
lines changed

packages/context/src/binding.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export enum BindingScope {
3434
* req2.get('b1') ==> 3
3535
* app.get('b1') ==> 4
3636
*/
37-
TRANSIENT,
37+
TRANSIENT = 'Transient',
3838

3939
/**
4040
* The binding provides a value as a singleton within each local context. The
@@ -59,7 +59,7 @@ export enum BindingScope {
5959
* // 2 is the singleton for req2 afterward
6060
* req2.get('b1') ==> 2
6161
*/
62-
CONTEXT,
62+
CONTEXT = 'Context',
6363

6464
/**
6565
* The binding provides a value as a singleton within the context hierarchy
@@ -82,7 +82,14 @@ export enum BindingScope {
8282
* // 'b1' is found in app, reuse it
8383
* req2.get('b1') ==> 0
8484
*/
85-
SINGLETON,
85+
SINGLETON = 'Singleton',
86+
}
87+
88+
export enum BindingType {
89+
CONSTANT = 'Constant',
90+
DYNAMIC_VALUE = 'DynamicValue',
91+
CLASS = 'Class',
92+
PROVIDER = 'Provider',
8693
}
8794

8895
// FIXME(bajtos) The binding class should be parameterized by the value
@@ -127,6 +134,7 @@ export class Binding {
127134
public readonly key: string;
128135
public readonly tags: Set<string> = new Set();
129136
public scope: BindingScope = BindingScope.TRANSIENT;
137+
public type: BindingType;
130138

131139
private _cache: BoundValue;
132140
private _getValue: (ctx?: Context) => BoundValue | Promise<BoundValue>;
@@ -243,8 +251,9 @@ export class Binding {
243251
return this;
244252
}
245253

246-
inScope(scope: BindingScope) {
254+
inScope(scope: BindingScope): this {
247255
this.scope = scope;
256+
return this;
248257
}
249258

250259
/**
@@ -259,6 +268,7 @@ export class Binding {
259268
* ```
260269
*/
261270
to(value: BoundValue): this {
271+
this.type = BindingType.CONSTANT;
262272
this._getValue = () => value;
263273
return this;
264274
}
@@ -282,7 +292,7 @@ export class Binding {
282292
* ```
283293
*/
284294
toDynamicValue(factoryFn: () => BoundValue | Promise<BoundValue>): this {
285-
// TODO(bajtos) allow factoryFn with @inject arguments
295+
this.type = BindingType.DYNAMIC_VALUE;
286296
this._getValue = ctx => factoryFn();
287297
return this;
288298
}
@@ -304,6 +314,7 @@ export class Binding {
304314
* @param provider The value provider to use.
305315
*/
306316
public toProvider<T>(providerClass: Constructor<Provider<T>>): this {
317+
this.type = BindingType.PROVIDER;
307318
this._getValue = ctx => {
308319
const providerOrPromise = instantiateClass<Provider<T>>(
309320
providerClass,
@@ -326,6 +337,7 @@ export class Binding {
326337
* we can resolve them from the context.
327338
*/
328339
toClass<T>(ctor: Constructor<T>): this {
340+
this.type = BindingType.CLASS;
329341
this._getValue = ctx => instantiateClass(ctor, ctx!);
330342
this.valueConstructor = ctor;
331343
return this;
@@ -335,4 +347,18 @@ export class Binding {
335347
this.isLocked = false;
336348
return this;
337349
}
350+
351+
toJSON(): Object {
352+
// tslint:disable-next-line:no-any
353+
const json: {[name: string]: any} = {
354+
key: this.key,
355+
scope: this.scope,
356+
tags: Array.from(this.tags),
357+
isLocked: this.isLocked,
358+
};
359+
if (this.type != null) {
360+
json.type = this.type;
361+
}
362+
return json;
363+
}
338364
}

packages/context/src/context.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,17 @@ export class Context {
192192

193193
return getDeepProperty(boundValue, path);
194194
}
195+
196+
/**
197+
* Create a plain JSON object for the context
198+
*/
199+
toJSON(): Object {
200+
const json: {[key: string]: Object} = {};
201+
for (const [k, v] of this.registry) {
202+
json[k] = v.toJSON();
203+
}
204+
return json;
205+
}
195206
}
196207

197208
function getDeepProperty(value: BoundValue, path: string) {

packages/context/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
export {Binding, BindingScope, BoundValue, ValueOrPromise} from './binding';
6+
export {
7+
Binding,
8+
BindingScope,
9+
BindingType,
10+
BoundValue,
11+
ValueOrPromise,
12+
} from './binding';
13+
714
export {Context} from './context';
815
export {Constructor} from './resolver';
916
export {inject, Setter, Getter} from './inject';

packages/context/test/unit/binding.ts

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
import {expect} from '@loopback/testlab';
7-
import {Binding, BindingScope, Context, inject, Provider} from '../..';
7+
import {
8+
Binding,
9+
BindingScope,
10+
BindingType,
11+
Context,
12+
inject,
13+
Provider,
14+
} from '../..';
815

916
const key = 'foo';
1017

@@ -73,40 +80,105 @@ describe('Binding', () => {
7380
binding.to('value');
7481
expect(binding.getValue(ctx)).to.equal('value');
7582
});
83+
84+
it('sets the type to CONSTANT', () => {
85+
binding.to('value');
86+
expect(binding.type).to.equal(BindingType.CONSTANT);
87+
});
88+
});
89+
90+
describe('toDynamicValue(dynamicValueFn)', () => {
91+
it('support a factory', async () => {
92+
const b = ctx.bind('msg').toDynamicValue(() => Promise.resolve('hello'));
93+
const value: string = await ctx.get('msg');
94+
expect(value).to.equal('hello');
95+
expect(b.type).to.equal(BindingType.DYNAMIC_VALUE);
96+
});
97+
});
98+
99+
describe('toClass(cls)', () => {
100+
it('support a class', async () => {
101+
ctx.bind('msg').toDynamicValue(() => Promise.resolve('world'));
102+
const b = ctx.bind('myService').toClass(MyService);
103+
expect(b.type).to.equal(BindingType.CLASS);
104+
const myService: MyService = await ctx.get('myService');
105+
expect(myService.getMessage()).to.equal('hello world');
106+
});
76107
});
77108

78109
describe('toProvider(provider)', () => {
79110
it('binding returns the expected value', async () => {
80111
ctx.bind('msg').to('hello');
81112
ctx.bind('provider_key').toProvider(MyProvider);
82-
const value: String = await ctx.get('provider_key');
113+
const value: string = await ctx.get('provider_key');
83114
expect(value).to.equal('hello world');
84115
});
85116

86117
it('can resolve provided value synchronously', () => {
87118
ctx.bind('msg').to('hello');
88119
ctx.bind('provider_key').toProvider(MyProvider);
89-
const value: String = ctx.getSync('provider_key');
120+
const value: string = ctx.getSync('provider_key');
90121
expect(value).to.equal('hello world');
91122
});
92123

93124
it('support asynchronous dependencies of provider class', async () => {
94125
ctx.bind('msg').toDynamicValue(() => Promise.resolve('hello'));
95126
ctx.bind('provider_key').toProvider(MyProvider);
96-
const value: String = await ctx.get('provider_key');
127+
const value: string = await ctx.get('provider_key');
97128
expect(value).to.equal('hello world');
98129
});
130+
131+
it('sets the type to PROVIDER', () => {
132+
ctx.bind('msg').to('hello');
133+
const b = ctx.bind('provider_key').toProvider(MyProvider);
134+
expect(b.type).to.equal(BindingType.PROVIDER);
135+
});
136+
});
137+
138+
describe('toJSON()', () => {
139+
it('converts a keyed binding to plain JSON object', () => {
140+
const json = binding.toJSON();
141+
expect(json).to.eql({
142+
key: key,
143+
scope: BindingScope.TRANSIENT,
144+
tags: [],
145+
isLocked: false,
146+
});
147+
});
148+
149+
it('converts a binding with more attributes to plain JSON object', () => {
150+
const myBinding = new Binding(key, true)
151+
.inScope(BindingScope.CONTEXT)
152+
.tag('model')
153+
.to('a');
154+
const json = myBinding.toJSON();
155+
expect(json).to.eql({
156+
key: key,
157+
scope: BindingScope.CONTEXT,
158+
tags: ['model'],
159+
isLocked: true,
160+
type: BindingType.CONSTANT,
161+
});
162+
});
99163
});
100164

101165
function givenBinding() {
102166
ctx = new Context();
103167
binding = new Binding(key);
104168
}
105169

106-
class MyProvider implements Provider<String> {
170+
class MyProvider implements Provider<string> {
107171
constructor(@inject('msg') private _msg: string) {}
108-
value(): String {
172+
value(): string {
109173
return this._msg + ' world';
110174
}
111175
}
176+
177+
class MyService {
178+
constructor(@inject('msg') private _msg: string) {}
179+
180+
getMessage(): string {
181+
return 'hello ' + this._msg;
182+
}
183+
}
112184
});

packages/context/test/unit/context.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
import {expect} from '@loopback/testlab';
7-
import {Context, Binding, BindingScope, isPromise} from '../..';
7+
import {Context, Binding, BindingScope, BindingType, isPromise} from '../..';
88

99
describe('Context', () => {
1010
let ctx: Context;
@@ -375,6 +375,47 @@ describe('Context', () => {
375375
});
376376
});
377377

378+
describe('toJSON()', () => {
379+
it('converts to plain JSON object', () => {
380+
ctx
381+
.bind('a')
382+
.to('1')
383+
.lock();
384+
ctx
385+
.bind('b')
386+
.toDynamicValue(() => 2)
387+
.inScope(BindingScope.SINGLETON)
388+
.tag(['X', 'Y']);
389+
ctx
390+
.bind('c')
391+
.to(3)
392+
.tag('Z');
393+
expect(ctx.toJSON()).to.eql({
394+
a: {
395+
key: 'a',
396+
scope: BindingScope.TRANSIENT,
397+
tags: [],
398+
isLocked: true,
399+
type: BindingType.CONSTANT,
400+
},
401+
b: {
402+
key: 'b',
403+
scope: BindingScope.SINGLETON,
404+
tags: ['X', 'Y'],
405+
isLocked: false,
406+
type: BindingType.DYNAMIC_VALUE,
407+
},
408+
c: {
409+
key: 'c',
410+
scope: BindingScope.TRANSIENT,
411+
tags: ['Z'],
412+
isLocked: false,
413+
type: BindingType.CONSTANT,
414+
},
415+
});
416+
});
417+
});
418+
378419
function createContext() {
379420
ctx = new Context();
380421
}

0 commit comments

Comments
 (0)