Skip to content

Commit 0aca056

Browse files
authored
Add functionality to accept a JSON template string when initializing a RC server template (#2520)
* Add support to pass in a json string for RC server template initialization * Merge ssrc changes * Apply lint changes * Update comments * Run api-extractor:local * Update comments and address feedback * Update inline comment and lint fixes * Revert toJSON functionality for ServerTemplate
1 parent 36db280 commit 0aca056

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed

etc/firebase-admin.remote-config.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface InAppDefaultValue {
4040

4141
// @public
4242
export interface InitServerTemplateOptions extends GetServerTemplateOptions {
43-
template?: ServerTemplateData;
43+
template?: ServerTemplateData | string;
4444
}
4545

4646
// @public

src/remote-config/remote-config-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,10 @@ export interface InitServerTemplateOptions extends GetServerTemplateOptions {
375375
* example, customers can reduce initialization latency by pre-fetching and
376376
* caching template data and then using this option to initialize the SDK with
377377
* that data.
378+
* The template can be initialized with either a {@link ServerTemplateData}
379+
* object or a JSON string.
378380
*/
379-
template?: ServerTemplateData,
381+
template?: ServerTemplateData|string,
380382
}
381383

382384
/**

src/remote-config/remote-config.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
GetServerTemplateOptions,
4040
InitServerTemplateOptions,
4141
} from './remote-config-api';
42+
import { isString } from 'lodash';
4243

4344
/**
4445
* The Firebase `RemoteConfig` service interface.
@@ -198,7 +199,19 @@ export class RemoteConfig {
198199
const template = new ServerTemplateImpl(
199200
this.client, new ConditionEvaluator(), options?.defaultConfig);
200201
if (options?.template) {
201-
template.cache = options?.template;
202+
// Check and instantiates the template via a json string
203+
if (isString(options?.template)) {
204+
try {
205+
template.cache = new ServerTemplateDataImpl(JSON.parse(options?.template));
206+
} catch (e) {
207+
throw new FirebaseRemoteConfigError(
208+
'invalid-argument',
209+
`Failed to parse the JSON string: ${options?.template}. ` + e
210+
);
211+
}
212+
} else {
213+
template.cache = options?.template;
214+
}
202215
}
203216
return template;
204217
}
@@ -430,7 +443,6 @@ class ServerTemplateDataImpl implements ServerTemplateData {
430443
}
431444

432445
this.etag = template.etag;
433-
434446
if (typeof template.parameters !== 'undefined') {
435447
if (!validator.isNonNullObject(template.parameters)) {
436448
throw new FirebaseRemoteConfigError(

test/unit/remote-config/remote-config.spec.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
} from '../../../src/remote-config/remote-config-api-client-internal';
3636
import { deepCopy } from '../../../src/utils/deep-copy';
3737
import {
38-
NamedCondition, ServerTemplate, ServerTemplateData
38+
NamedCondition, ServerTemplate, ServerTemplateData, Version
3939
} from '../../../src/remote-config/remote-config-api';
4040

4141
const expect = chai.expect;
@@ -648,10 +648,61 @@ describe('RemoteConfig', () => {
648648
valueType: 'STRING'
649649
}
650650
};
651-
const initializedTemplate = remoteConfig.initServerTemplate({ template }).cache;
652-
const parsed = JSON.parse(JSON.stringify(initializedTemplate));
651+
const initializedTemplate = remoteConfig.initServerTemplate({ template });
652+
const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache));
653653
expect(parsed).deep.equals(deepCopy(template));
654654
});
655+
656+
it('should set and instantiates template when json string is passed', () => {
657+
const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData;
658+
template.parameters = {
659+
dog_type: {
660+
defaultValue: {
661+
value: 'shiba'
662+
},
663+
description: 'Type of dog breed',
664+
valueType: 'STRING'
665+
}
666+
};
667+
const templateJson = JSON.stringify(template);
668+
const initializedTemplate = remoteConfig.initServerTemplate({ template: templateJson });
669+
const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache));
670+
const expectedVersion = deepCopy(VERSION_INFO);
671+
expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString();
672+
template.version = expectedVersion as Version;
673+
expect(parsed).deep.equals(deepCopy(template));
674+
});
675+
676+
describe('should throw error if invalid template JSON is passed', () => {
677+
const INVALID_PARAMETERS: any[] = [null, '', 'abc', 1, true, []];
678+
const INVALID_CONDITIONS: any[] = [null, '', 'abc', 1, true, {}];
679+
680+
let sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
681+
const jsonString = '{invalidJson: null}';
682+
it('should throw if template is an invalid JSON', () => {
683+
expect(() => remoteConfig.initServerTemplate({ template: jsonString }))
684+
.to.throw(/Failed to parse the JSON string: ([\D\w]*)\./);
685+
});
686+
687+
INVALID_PARAMETERS.forEach((invalidParameter) => {
688+
sourceTemplate.parameters = invalidParameter;
689+
const jsonString = JSON.stringify(sourceTemplate);
690+
it(`should throw if the parameters is ${JSON.stringify(invalidParameter)}`, () => {
691+
expect(() => remoteConfig.initServerTemplate({ template: jsonString }))
692+
.to.throw('Remote Config parameters must be a non-null object');
693+
});
694+
});
695+
696+
sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE);
697+
INVALID_CONDITIONS.forEach((invalidConditions) => {
698+
sourceTemplate.conditions = invalidConditions;
699+
const jsonString = JSON.stringify(sourceTemplate);
700+
it(`should throw if the conditions is ${JSON.stringify(invalidConditions)}`, () => {
701+
expect(() => remoteConfig.initServerTemplate({ template: jsonString }))
702+
.to.throw('Remote Config conditions must be an array');
703+
});
704+
});
705+
});
655706
});
656707

657708
describe('RemoteConfigServerTemplate', () => {
@@ -1039,12 +1090,12 @@ describe('RemoteConfig', () => {
10391090
it('uses local default if parameter not in template', () => {
10401091
const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData;
10411092
template.parameters = {};
1042-
1093+
10431094
const stub = sinon
10441095
.stub(RemoteConfigApiClient.prototype, 'getServerTemplate')
10451096
.resolves(template);
10461097
stubs.push(stub);
1047-
1098+
10481099
const defaultConfig = {
10491100
dog_coat: 'blue merle',
10501101
};
@@ -1061,12 +1112,12 @@ describe('RemoteConfig', () => {
10611112
template.parameters = {
10621113
dog_no_remote_default_value: {}
10631114
};
1064-
1115+
10651116
const stub = sinon
10661117
.stub(RemoteConfigApiClient.prototype, 'getServerTemplate')
10671118
.resolves(template);
10681119
stubs.push(stub);
1069-
1120+
10701121
const defaultConfig = {
10711122
dog_no_remote_default_value: 'local default'
10721123
};
@@ -1083,7 +1134,7 @@ describe('RemoteConfig', () => {
10831134
template.parameters = {
10841135
dog_no_remote_default_value: {}
10851136
};
1086-
1137+
10871138
const stub = sinon
10881139
.stub(RemoteConfigApiClient.prototype, 'getServerTemplate')
10891140
.resolves(template);

0 commit comments

Comments
 (0)