Skip to content

Commit

Permalink
feat: Add typed variation methods. (#288)
Browse files Browse the repository at this point in the history
Co-authored-by: Yusinto Ngadiman <[email protected]>
resolves #285
  • Loading branch information
kinyoklion authored Sep 27, 2023
1 parent dda6b37 commit 8e96a52
Show file tree
Hide file tree
Showing 10 changed files with 471 additions and 33 deletions.
3 changes: 2 additions & 1 deletion contract-tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ app.get('/', (req, res) => {
'migrations',
'event-sampling',
'config-override-kind',
'metric-kind'
'metric-kind',
'strongly-typed',
],
});
});
Expand Down
28 changes: 24 additions & 4 deletions contract-tests/sdkClientEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,30 @@ export async function newSdkClientEntity(options) {
case 'evaluate': {
const pe = params.evaluate;
if (pe.detail) {
return await client.variationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
switch(pe.valueType) {
case "bool":
return await client.boolVariationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
case "int": // Intentional fallthrough.
case "double":
return await client.numberVariationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
case "string":
return await client.stringVariationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
default:
return await client.variationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
}

} else {
const value = await client.variation(pe.flagKey, pe.context || pe.user, pe.defaultValue);
return { value };
switch(pe.valueType) {
case "bool":
return {value: await client.boolVariation(pe.flagKey, pe.context || pe.user, pe.defaultValue)};
case "int": // Intentional fallthrough.
case "double":
return {value: await client.numberVariation(pe.flagKey, pe.context || pe.user, pe.defaultValue)};
case "string":
return {value: await client.stringVariation(pe.flagKey, pe.context || pe.user, pe.defaultValue)};
default:
return {value: await client.variation(pe.flagKey, pe.context || pe.user, pe.defaultValue)};
}
}
}

Expand Down Expand Up @@ -160,7 +180,7 @@ export async function newSdkClientEntity(options) {

case 'migrationVariation':
const migrationVariation = params.migrationVariation;
const res = await client.variationMigration(
const res = await client.migrationVariation(
migrationVariation.key,
migrationVariation.context,
migrationVariation.defaultStage,
Expand Down
19 changes: 19 additions & 0 deletions packages/shared/common/src/api/data/LDEvaluationDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,22 @@ export interface LDEvaluationDetail {
*/
reason: LDEvaluationReason;
}

export interface LDEvaluationDetailTyped<TFlag> {
/**
* The result of the flag evaluation. This will be either one of the flag's variations or
* the default value that was passed to `LDClient.variationDetail`.
*/
value: TFlag;

/**
* The index of the returned value within the flag's list of variations, e.g. 0 for the
* first variation-- or `null` if the default value was returned.
*/
variationIndex?: number | null;

/**
* An object describing the main factor that influenced the flag evaluation value.
*/
reason: LDEvaluationReason;
}
93 changes: 93 additions & 0 deletions packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,99 @@ describe('given an LDClient with test data', () => {
const valueB = await client.variation('my-feature-flag-1', userContextObject, 'default');
expect(valueB).toEqual(true);
});

it('evaluates with jsonVariation', async () => {
td.update(td.flag('flagkey').booleanFlag().on(true));
const boolRes: boolean = (await client.jsonVariation('flagkey', defaultUser, false)) as boolean;
expect(boolRes).toBe(true);

td.update(td.flag('flagkey').valueForAll(62));
const numericRes: number = (await client.jsonVariation(
'flagkey',
defaultUser,
false,
)) as number;
expect(numericRes).toBe(62);

td.update(td.flag('flagkey').valueForAll('potato'));
const stringRes: string = (await client.jsonVariation('flagkey', defaultUser, false)) as string;
expect(stringRes).toBe('potato');
});

it('evaluates an existing boolean flag', async () => {
td.update(td.flag('flagkey').booleanFlag().on(true));
expect(await client.boolVariation('flagkey', defaultUser, false)).toEqual(true);
});

it('it uses the default value when a boolean variation is for a flag of the wrong type', async () => {
td.update(td.flag('flagkey').valueForAll('potato'));
expect(await client.boolVariation('flagkey', defaultUser, false)).toEqual(false);
});

it('evaluates an existing numeric flag', async () => {
td.update(td.flag('flagkey').booleanFlag().valueForAll(18));
expect(await client.numberVariation('flagkey', defaultUser, 36)).toEqual(18);
});

it('it uses the default value when a numeric variation is for a flag of the wrong type', async () => {
td.update(td.flag('flagkey').valueForAll('potato'));
expect(await client.numberVariation('flagkey', defaultUser, 36)).toEqual(36);
});

it('evaluates an existing string flag', async () => {
td.update(td.flag('flagkey').booleanFlag().valueForAll('potato'));
expect(await client.stringVariation('flagkey', defaultUser, 'default')).toEqual('potato');
});

it('it uses the default value when a string variation is for a flag of the wrong type', async () => {
td.update(td.flag('flagkey').valueForAll(8));
expect(await client.stringVariation('flagkey', defaultUser, 'default')).toEqual('default');
});

it('evaluates an existing boolean flag with detail', async () => {
td.update(td.flag('flagkey').booleanFlag().on(true));
const res = await client.boolVariationDetail('flagkey', defaultUser, false);
expect(res.value).toEqual(true);
expect(res.reason.kind).toBe('FALLTHROUGH');
});

it('it uses the default value when a boolean variation is for a flag of the wrong type with detail', async () => {
td.update(td.flag('flagkey').valueForAll('potato'));
const res = await client.boolVariationDetail('flagkey', defaultUser, false);
expect(res.value).toEqual(false);
expect(res.reason.kind).toEqual('ERROR');
expect(res.reason.errorKind).toEqual('WRONG_TYPE');
});

it('evaluates an existing numeric flag with detail', async () => {
td.update(td.flag('flagkey').booleanFlag().valueForAll(18));
const res = await client.numberVariationDetail('flagkey', defaultUser, 36);
expect(res.value).toEqual(18);
expect(res.reason.kind).toBe('FALLTHROUGH');
});

it('it uses the default value when a numeric variation is for a flag of the wrong type with detail', async () => {
td.update(td.flag('flagkey').valueForAll('potato'));
const res = await client.numberVariationDetail('flagkey', defaultUser, 36);
expect(res.value).toEqual(36);
expect(res.reason.kind).toEqual('ERROR');
expect(res.reason.errorKind).toEqual('WRONG_TYPE');
});

it('evaluates an existing string flag with detail', async () => {
td.update(td.flag('flagkey').booleanFlag().valueForAll('potato'));
const res = await client.stringVariationDetail('flagkey', defaultUser, 'default');
expect(res.value).toEqual('potato');
expect(res.reason.kind).toBe('FALLTHROUGH');
});

it('it uses the default value when a string variation is for a flag of the wrong type with detail', async () => {
td.update(td.flag('flagkey').valueForAll(8));
const res = await client.stringVariationDetail('flagkey', defaultUser, 'default');
expect(res.value).toEqual('default');
expect(res.reason.kind).toEqual('ERROR');
expect(res.reason.errorKind).toEqual('WRONG_TYPE');
});
});

describe('given an offline client', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('given an LDClient with test data', () => {
const defaultValue = Object.values(LDMigrationStage).find((item) => item !== value);
// Verify the pre-condition that the default value is not the value under test.
expect(defaultValue).not.toEqual(value);
const res = await client.variationMigration(
const res = await client.migrationVariation(
flagKey,
{ key: 'test-key' },
defaultValue as LDMigrationStage,
Expand All @@ -74,15 +74,15 @@ describe('given an LDClient with test data', () => {
LDMigrationStage.RampDown,
LDMigrationStage.Complete,
])('returns the default value if the flag does not exist: default = %p', async (stage) => {
const res = await client.variationMigration('no-flag', { key: 'test-key' }, stage);
const res = await client.migrationVariation('no-flag', { key: 'test-key' }, stage);

expect(res.value).toEqual(stage);
});

it('produces an error event for a migration flag with an incorrect value', async () => {
const flagKey = 'bad-migration';
td.update(td.flag(flagKey).valueForAll('potato'));
const res = await client.variationMigration(flagKey, { key: 'test-key' }, LDMigrationStage.Off);
const res = await client.migrationVariation(flagKey, { key: 'test-key' }, LDMigrationStage.Off);
expect(res.value).toEqual(LDMigrationStage.Off);
expect(errors.length).toEqual(1);
expect(errors[0].message).toEqual(
Expand Down
Loading

0 comments on commit 8e96a52

Please sign in to comment.