Skip to content

Commit 69b1f7e

Browse files
feat: add support for custom deserializers
1 parent 1e47520 commit 69b1f7e

8 files changed

+34263
-202
lines changed

package-lock.json

Lines changed: 34037 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@angular/platform-browser": "^11.0.2",
2626
"@angular/platform-browser-dynamic": "^11.0.2",
2727
"@angular/router": "^11.0.2",
28-
"@hypertrace/hyperdash": "^1.1.2",
28+
"@hypertrace/hyperdash": "^1.2.0",
2929
"core-js": "^3.7.0",
3030
"lodash-es": "^4.17.15",
3131
"rxjs": "^6.6.3",

projects/hyperdash-angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"rxjs": "^6.6.3",
2222
"zone.js": "~0.10.3",
2323
"lodash-es": "^4.17.15",
24-
"@hypertrace/hyperdash": "^1.1.2"
24+
"@hypertrace/hyperdash": "^1.2.0"
2525
},
2626
"dependencies": {
2727
"tslib": "^2.0.3"

projects/hyperdash-angular/src/configuration/default-configuration.service.test.ts

Lines changed: 39 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@ import { Injectable } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
33
import {
44
ARRAY_PROPERTY,
5-
BOOLEAN_PROPERTY,
5+
Deserializer,
6+
JsonPrimitive,
67
LogMessage,
7-
ModelPropertyTypeRegistrationInformation,
8-
NUMBER_PROPERTY
8+
ModelPropertyTypeRegistrationInformation
99
} from '@hypertrace/hyperdash';
1010
import { DeserializationManagerService } from '../injectable-wrappers/deserialization/deserialization-manager.service';
1111
import { LoggerService } from '../injectable-wrappers/logger.service';
1212
import { ModelLibraryService } from '../injectable-wrappers/model-library.service';
1313
import { ModelManagerService } from '../injectable-wrappers/model-manager.service';
1414
import { ModelPropertyTypeLibraryService } from '../injectable-wrappers/model-property-type-library.service';
15-
import { ModelPropertyValidatorService } from '../injectable-wrappers/model-property-validator.service';
1615
import { SerializationManagerService } from '../injectable-wrappers/serialization/serialization-manager.service';
17-
import { VariableManagerService } from '../injectable-wrappers/variable-manager.service';
18-
import { MODEL_PROPERTY_TYPES } from '../module/dashboard-core.module';
16+
import { DASHBOARD_DESERIALIZERS, MODEL_PROPERTY_TYPES } from '../module/dashboard-core.module';
1917
import { DefaultConfigurationService } from './default-configuration.service';
2018

2119
describe('Default configuration service', () => {
@@ -29,6 +27,11 @@ describe('Default configuration service', () => {
2927
provide: MODEL_PROPERTY_TYPES,
3028
useValue: [{ type: 'test-property' }, TestPropertyTypeProvider],
3129
multi: true
30+
},
31+
{
32+
provide: DASHBOARD_DESERIALIZERS,
33+
useValue: [TestDeserializer],
34+
multi: true
3235
}
3336
]
3437
});
@@ -48,92 +51,6 @@ describe('Default configuration service', () => {
4851
logger.warn = jest.fn();
4952
});
5053

51-
test('correctly configures deserialization', () => {
52-
const deserializationManager = TestBed.inject(DeserializationManagerService);
53-
const modelLibrary = TestBed.inject(ModelLibraryService);
54-
55-
TestBed.inject(ModelPropertyValidatorService).setStrictSchema(false);
56-
const testModel = class ModelClass {
57-
public constructor(public prop: unknown) {}
58-
};
59-
60-
modelLibrary.registerModelClass(testModel, { type: 'test-model' });
61-
modelLibrary.registerModelProperty(testModel, 'prop', {
62-
type: BOOLEAN_PROPERTY.type,
63-
key: 'prop'
64-
});
65-
66-
// Should throw until we configure the deserialization
67-
expect(() => deserializationManager.deserialize({ type: 'test-model', prop: false })).toThrow();
68-
69-
defaultConfigurationService.configure();
70-
expect(deserializationManager.deserialize({ type: 'test-model', prop: false })).toEqual(new testModel(false));
71-
expect(deserializationManager.deserialize({ type: 'test-model', prop: [false] })).toEqual(new testModel([false]));
72-
expect(deserializationManager.deserialize({ type: 'test-model', prop: { nested: false } })).toEqual(
73-
new testModel({ nested: false })
74-
);
75-
76-
expect(
77-
deserializationManager.deserialize({
78-
type: 'test-model',
79-
prop: {
80-
type: 'test-model',
81-
prop: 'two models'
82-
}
83-
})
84-
).toEqual(new testModel(new testModel('two models')));
85-
86-
expect(
87-
deserializationManager.deserialize({
88-
type: 'test-model',
89-
prop: {
90-
nested: {
91-
type: 'test-model',
92-
prop: 'object sandwich'
93-
}
94-
}
95-
})
96-
).toEqual(new testModel({ nested: new testModel('object sandwich') }));
97-
});
98-
99-
test('correctly configures deserialization and setting of variables', () => {
100-
const deserializationManager = TestBed.inject(DeserializationManagerService);
101-
const modelLibrary = TestBed.inject(ModelLibraryService);
102-
103-
const testModel = class ModelClass {
104-
public constructor(public prop?: number) {}
105-
};
106-
107-
modelLibrary.registerModelClass(testModel, { type: 'test-model' });
108-
modelLibrary.registerModelProperty(testModel, 'prop', {
109-
type: NUMBER_PROPERTY.type,
110-
key: 'prop',
111-
required: false
112-
});
113-
114-
defaultConfigurationService.configure();
115-
116-
const deserializedModel = deserializationManager.deserialize<object>({
117-
type: 'test-model',
118-
// tslint:disable-next-line:no-invalid-template-strings
119-
prop: '${test}'
120-
});
121-
122-
expect(deserializedModel).toEqual(new testModel());
123-
124-
TestBed.inject(VariableManagerService).set('test', 42, deserializedModel);
125-
126-
expect(deserializedModel).toEqual(new testModel(42));
127-
});
128-
129-
test('should throw if attempting to configure twice', () => {
130-
defaultConfigurationService.configure();
131-
132-
expect(() => defaultConfigurationService.configure()).toThrow(
133-
'Default Configuration Service cannot be configured twice'
134-
);
135-
});
136-
13754
test('correctly configures serialization', () => {
13855
const serializationManager = TestBed.inject(SerializationManagerService);
13956
const modelLibrary = TestBed.inject(ModelLibraryService);
@@ -173,9 +90,26 @@ describe('Default configuration service', () => {
17390
const propertyTypeLibrary = TestBed.inject(ModelPropertyTypeLibraryService);
17491
propertyTypeLibrary.registerPropertyType = jest.fn();
17592
defaultConfigurationService.configure();
176-
expect(propertyTypeLibrary.registerPropertyType).toHaveBeenCalledWith({ type: 'test-property' });
17793

94+
expect(propertyTypeLibrary.registerPropertyType).toHaveBeenCalledTimes(2);
95+
expect(propertyTypeLibrary.registerPropertyType).toHaveBeenCalledWith({ type: 'test-property' });
17896
expect(propertyTypeLibrary.registerPropertyType).toHaveBeenCalledWith(expect.any(TestPropertyTypeProvider));
97+
98+
defaultConfigurationService.configure();
99+
// Should not be called a third time
100+
expect(propertyTypeLibrary.registerPropertyType).toHaveBeenCalledTimes(2);
101+
});
102+
103+
test('registers provided deserializers', () => {
104+
const deserializationManager = TestBed.inject(DeserializationManagerService);
105+
deserializationManager.registerDeserializer = jest.fn();
106+
defaultConfigurationService.configure();
107+
expect(deserializationManager.registerDeserializer).toHaveBeenCalledTimes(1);
108+
expect(deserializationManager.registerDeserializer).toHaveBeenCalledWith(expect.any(TestDeserializer));
109+
110+
defaultConfigurationService.configure();
111+
// Should not be called a second time
112+
expect(deserializationManager.registerDeserializer).toHaveBeenCalledTimes(1);
179113
});
180114
});
181115

@@ -185,3 +119,15 @@ describe('Default configuration service', () => {
185119
class TestPropertyTypeProvider implements ModelPropertyTypeRegistrationInformation {
186120
public readonly type: string = 'test-prop-provider';
187121
}
122+
123+
@Injectable({
124+
providedIn: 'root'
125+
})
126+
class TestDeserializer implements Deserializer<string, string> {
127+
public canDeserialize(json: JsonPrimitive): json is string {
128+
return typeof json === 'string';
129+
}
130+
public deserialize(json: string): string {
131+
return json.toUpperCase();
132+
}
133+
}

projects/hyperdash-angular/src/configuration/default-configuration.service.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import { Inject, Injectable, Injector, Type } from '@angular/core';
2-
import { ModelPropertyTypeRegistrationInformation } from '@hypertrace/hyperdash';
1+
import { AbstractType, Inject, Injectable, Injector, Type } from '@angular/core';
2+
import { Deserializer, ModelPropertyTypeRegistrationInformation } from '@hypertrace/hyperdash';
33
import { flatten, uniq } from 'lodash-es';
44
import { DefaultModelApiBuilderService } from '../injectable-wrappers/default-model-api-builder.service';
5-
import { ArrayDeserializerService } from '../injectable-wrappers/deserialization/array-deserializer.service';
65
import { DeserializationManagerService } from '../injectable-wrappers/deserialization/deserialization-manager.service';
7-
import { ModelDeserializerService } from '../injectable-wrappers/deserialization/model-deserializer.service';
8-
import { ObjectDeserializerService } from '../injectable-wrappers/deserialization/object-deserializer.service';
9-
import { PrimitiveDeserializerService } from '../injectable-wrappers/deserialization/primitive-deserializer.service';
10-
import { VariableDeserializerService } from '../injectable-wrappers/deserialization/variable-deserializer.service';
116
import { ModelEventInstallerService } from '../injectable-wrappers/model-event-installer.service';
127
import { ModelManagerService } from '../injectable-wrappers/model-manager.service';
138
import { ModelPropertyTypeLibraryService } from '../injectable-wrappers/model-property-type-library.service';
@@ -18,7 +13,7 @@ import { PrimitiveSerializerService } from '../injectable-wrappers/serialization
1813
import { SerializationManagerService } from '../injectable-wrappers/serialization/serialization-manager.service';
1914
import { VariableSerializerService } from '../injectable-wrappers/serialization/variable-serializer.service';
2015
import { ModelInjectService } from '../model/decorators/model-inject.service';
21-
import { MODEL_PROPERTY_TYPES } from '../module/dashboard-core.module';
16+
import { DASHBOARD_DESERIALIZERS, MODEL_PROPERTY_TYPES } from '../module/dashboard-core.module';
2217

2318
/**
2419
* Configures dashboard services for default behavior
@@ -28,14 +23,10 @@ import { MODEL_PROPERTY_TYPES } from '../module/dashboard-core.module';
2823
})
2924
export class DefaultConfigurationService {
3025
private configured: boolean = false;
26+
private readonly registeredObjects: WeakSet<object> = new WeakSet();
3127

3228
public constructor(
3329
private readonly deserializationManager: DeserializationManagerService,
34-
private readonly objectDeserializer: ObjectDeserializerService,
35-
private readonly arrayDeserializer: ArrayDeserializerService,
36-
private readonly primitiveDeserializer: PrimitiveDeserializerService,
37-
private readonly modelDeserializer: ModelDeserializerService,
38-
private readonly variableDeserializer: VariableDeserializerService,
3930
private readonly modelManager: ModelManagerService,
4031
private readonly defaultModelApiBuilder: DefaultModelApiBuilderService,
4132
private readonly serializationManager: SerializationManagerService,
@@ -48,35 +39,43 @@ export class DefaultConfigurationService {
4839
private readonly modelEventInstaller: ModelEventInstallerService,
4940
private readonly modelInjectService: ModelInjectService,
5041
private readonly injector: Injector,
51-
@Inject(MODEL_PROPERTY_TYPES) private readonly propertyTypes: PropertyTypeRegistration[][]
42+
@Inject(MODEL_PROPERTY_TYPES) private readonly propertyTypes: PropertyTypeRegistration[][],
43+
@Inject(DASHBOARD_DESERIALIZERS)
44+
private readonly deserializers: (Type<Deserializer> | AbstractType<Deserializer>)[][]
5245
) {}
5346

5447
/**
55-
* Does the configuration. This should be called during application bootstrap.
48+
* Does the configuration. This should be called during application bootstrap. Later calls will
49+
* do incremental configuration.
5650
*
57-
* @throws Error if configure is called more than once
5851
*/
5952
public configure(): void {
53+
this.registerPropertyTypes();
54+
this.registerDeserializers();
55+
6056
if (this.configured) {
61-
throw new Error('Default Configuration Service cannot be configured twice');
57+
return;
6258
}
6359
this.configured = true;
64-
this.registerPropertyTypes();
65-
this.configureDeserialization();
60+
// Beyond here, code should only be invoked once
6661
this.configureSerialization();
6762
this.registerModelDecorators();
6863
this.configureModelApiBuilder();
6964
}
7065

71-
private configureDeserialization(): void {
72-
this.deserializationManager.registerDeserializer(this.variableDeserializer);
73-
this.deserializationManager.registerDeserializer(this.primitiveDeserializer);
74-
this.deserializationManager.registerDeserializer(this.modelDeserializer);
75-
this.deserializationManager.registerDeserializer(this.arrayDeserializer);
76-
this.deserializationManager.registerDeserializer(this.objectDeserializer);
66+
private registerDeserializers(): void {
67+
uniq(flatten(this.deserializers)).forEach(deserializerClass => {
68+
if (this.registeredObjects.has(deserializerClass)) {
69+
return;
70+
}
71+
this.registeredObjects.add(deserializerClass);
72+
73+
this.deserializationManager.registerDeserializer(this.injector.get(deserializerClass));
74+
});
7775
}
7876

7977
private configureSerialization(): void {
78+
// TODO - expose customizing serializers similar to deserializers
8079
this.serializationManager.registerSerializer(this.variableSerializer);
8180
this.serializationManager.registerSerializer(this.primitiveSerializer);
8281
this.serializationManager.registerSerializer(this.modelSerializer);
@@ -90,6 +89,11 @@ export class DefaultConfigurationService {
9089

9190
private registerPropertyTypes(): void {
9291
uniq(flatten(this.propertyTypes)).forEach(propertyType => {
92+
if (this.registeredObjects.has(propertyType)) {
93+
return;
94+
}
95+
this.registeredObjects.add(propertyType);
96+
9397
if (typeof propertyType === 'object') {
9498
this.propertyTypeLibrary.registerPropertyType(propertyType);
9599
} else {

projects/hyperdash-angular/src/injectable-wrappers/model-property-type.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ModelPropertyType } from '@hypertrace/hyperdash';
33
import { DeserializationManagerService } from './deserialization/deserialization-manager.service';
44
import { ModelManagerService } from './model-manager.service';
55
/**
6-
* Injectable implementation of `RendererLModelPropertyTypeibrary`
6+
* Injectable implementation of `ModelPropertyType`
77
*/
88
@Injectable({
99
providedIn: 'root'

0 commit comments

Comments
 (0)