Skip to content

Commit 944137f

Browse files
Adam Simonadams85
authored andcommitted
Improve evaluation context to user object mapping
Signed-off-by: Adam Simon <[email protected]>
1 parent 8faf0ee commit 944137f

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

libs/shared/config-cat-core/src/lib/context-transformer.spec.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ describe('context-transformer', () => {
1010

1111
const user = {
1212
identifier: context['targetingKey'],
13-
country: undefined,
14-
custom: {},
1513
email: undefined,
14+
country: undefined,
15+
custom: {
16+
targetingKey: context['targetingKey']
17+
},
1618
};
1719

1820
expect(transformContext(context)).toEqual(user);
@@ -29,7 +31,11 @@ describe('context-transformer', () => {
2931
identifier: context['targetingKey'],
3032
email: context['email'],
3133
country: context['country'],
32-
custom: {},
34+
custom: {
35+
targetingKey: context['targetingKey'],
36+
email: context['email'],
37+
country: context['country'],
38+
},
3339
};
3440

3541
expect(transformContext(context)).toEqual(user);
@@ -44,14 +50,15 @@ describe('context-transformer', () => {
4450
const user = {
4551
identifier: context['targetingKey'],
4652
custom: {
53+
targetingKey: context['targetingKey'],
4754
customString: context['customString'],
4855
},
4956
};
5057

5158
expect(transformContext(context)).toEqual(user);
5259
});
5360

54-
it('map custom property with number to number', () => {
61+
it('map custom property with number', () => {
5562
const context: EvaluationContext = {
5663
targetingKey: 'test',
5764
customNumber: 1,
@@ -60,14 +67,15 @@ describe('context-transformer', () => {
6067
const user = {
6168
identifier: context['targetingKey'],
6269
custom: {
63-
customNumber: 1,
70+
targetingKey: context['targetingKey'],
71+
customNumber: context['customNumber'],
6472
},
6573
};
6674

6775
expect(transformContext(context)).toEqual(user);
6876
});
6977

70-
it('map custom property with boolean to string', () => {
78+
it('map custom property with boolean', () => {
7179
const context: EvaluationContext = {
7280
targetingKey: 'test',
7381
customBoolean: true,
@@ -76,7 +84,8 @@ describe('context-transformer', () => {
7684
const user = {
7785
identifier: context['targetingKey'],
7886
custom: {
79-
customBoolean: 'true',
87+
targetingKey: context['targetingKey'],
88+
customBoolean: context['customBoolean'],
8089
},
8190
};
8291

@@ -95,6 +104,7 @@ describe('context-transformer', () => {
95104
const user = {
96105
identifier: context['targetingKey'],
97106
custom: {
107+
targetingKey: context['targetingKey'],
98108
customObject: JSON.stringify(context['customObject']),
99109
},
100110
};
@@ -111,7 +121,8 @@ describe('context-transformer', () => {
111121
const user = {
112122
identifier: context['targetingKey'],
113123
custom: {
114-
customArray: ['one', 'two', 'three'],
124+
targetingKey: context['targetingKey'],
125+
customArray: context['customArray'],
115126
},
116127
};
117128

@@ -127,6 +138,7 @@ describe('context-transformer', () => {
127138
const user = {
128139
identifier: context['targetingKey'],
129140
custom: {
141+
targetingKey: context['targetingKey'],
130142
customArray: JSON.stringify(context['customArray']),
131143
},
132144
};
@@ -154,11 +166,29 @@ describe('context-transformer', () => {
154166
email: 'email',
155167
country: 'country',
156168
custom: {
157-
customString: 'customString',
158-
customBoolean: 'true',
159-
customNumber: 1,
160-
customObject: '{"prop1":"1","prop2":2}',
161-
customArray: '[1,"2",false]',
169+
targetingKey: context['targetingKey'],
170+
email: context['email'],
171+
country: context['country'],
172+
customString: context['customString'],
173+
customBoolean: context['customBoolean'],
174+
customNumber: context['customNumber'],
175+
customObject: JSON.stringify(context['customObject']),
176+
customArray: JSON.stringify(context['customArray']),
177+
},
178+
};
179+
180+
expect(transformContext(context)).toEqual(user);
181+
});
182+
183+
it('map identifier if targetingKey is not present', () => {
184+
const context: EvaluationContext = {
185+
identifier: 'test',
186+
};
187+
188+
const user = {
189+
identifier: 'test',
190+
custom: {
191+
identifier: context['identifier'],
162192
},
163193
};
164194

libs/shared/config-cat-core/src/lib/context-transformer.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,28 @@ import type { EvaluationContext, EvaluationContextValue } from '@openfeature/cor
22
import type { IUser as ConfigCatUser, UserAttributeValue } from '@configcat/sdk';
33

44
function toUserAttributeValue(value: EvaluationContextValue): UserAttributeValue {
5-
if (typeof value === 'string' || typeof value === 'number' || value instanceof Date) {
6-
return value;
7-
} else if (Array.isArray(value) && value.every((item) => typeof item === 'string')) {
8-
return value as ReadonlyArray<string>;
5+
// NOTE: The ConfigCat SDK doesn't support objects and non-string arrays as user attribute values
6+
// but we special-case these for backward compatibility.
7+
if (typeof value === "object"
8+
&& (!Array.isArray(value) || !value.every((item) => typeof item === 'string'))) {
9+
return JSON.stringify(value);
910
}
10-
return JSON.stringify(value);
11-
}
1211

13-
function transformCustomContextValues(contextValue: EvaluationContextValue): ConfigCatUser['custom'] {
14-
if (typeof contextValue !== 'object' || contextValue === null) {
15-
return {};
16-
}
12+
// NOTE: No need to check for unsupported attribute values as the ConfigCat SDK handles those internally.
13+
return value as UserAttributeValue;
14+
}
1715

18-
return Object.entries(contextValue).reduce<ConfigCatUser['custom']>(
19-
(context, [key, value]) => {
16+
function transformCustomContextValues(values: Record<string, EvaluationContextValue>): ConfigCatUser["custom"] {
17+
let attributes: ConfigCatUser["custom"];
18+
for (const [key, value] of Object.entries(values)) {
19+
if (value != null) {
2020
const transformedValue = toUserAttributeValue(value);
21-
return transformedValue ? { ...context, [key]: transformedValue } : context;
22-
},
23-
{} as ConfigCatUser['custom'],
24-
);
21+
// NOTE: No need to check for `identifier`, `email` and `country` user attributes
22+
// as the ConfigCat SDK ignores those as custom user attributes.
23+
(attributes ??= {})[key] = transformedValue;
24+
}
25+
}
26+
return attributes;
2527
}
2628

2729
function stringOrUndefined(param?: unknown): string | undefined {
@@ -33,16 +35,13 @@ function stringOrUndefined(param?: unknown): string | undefined {
3335
}
3436

3537
export function transformContext(context: EvaluationContext): ConfigCatUser | undefined {
36-
const { targetingKey, email, country, ...attributes } = context;
37-
38-
if (!targetingKey) {
39-
return undefined;
40-
}
38+
const { targetingKey, email, country } = context;
4139

4240
return {
43-
identifier: targetingKey,
41+
// NOTE: The ConfigCat SDK handles missing or unsupported identifier values.
42+
identifier: targetingKey ?? context["identifier"] as string,
4443
email: stringOrUndefined(email),
4544
country: stringOrUndefined(country),
46-
custom: transformCustomContextValues(attributes),
45+
custom: transformCustomContextValues(context),
4746
};
4847
}

0 commit comments

Comments
 (0)