diff --git a/packages/client-http/src/client/ConfidenceClient.test.ts b/packages/client-http/src/client/ConfidenceClient.test.ts index 0b7ded3a..b94a4ecb 100644 --- a/packages/client-http/src/client/ConfidenceClient.test.ts +++ b/packages/client-http/src/client/ConfidenceClient.test.ts @@ -1,6 +1,5 @@ import { AppliedFlag, ConfidenceClient } from './ConfidenceClient'; import { Configuration, ResolveContext } from './Configuration'; -import { ConfidenceFlag } from './ConfidenceFlag'; describe('ConfidenceClient', () => { const mockFetch = jest.fn(); @@ -97,7 +96,19 @@ describe('ConfidenceClient', () => { const config = await instanceUnderTest.resolve(context); expect(config).toEqual({ - flags: { ['test-flag']: new ConfidenceFlag(fakeFlag) }, + flags: { + ['test-flag']: { + flagName: 'test-flag', + schema: { + str: 'string', + }, + value: { + str: 'test', + }, + reason: Configuration.ResolveReason.Match, + variant: 'test', + }, + }, resolveToken: 'resolve-token', context, }); diff --git a/packages/client-http/src/client/ConfidenceClient.ts b/packages/client-http/src/client/ConfidenceClient.ts index e83d5f1d..f6db9e0f 100644 --- a/packages/client-http/src/client/ConfidenceClient.ts +++ b/packages/client-http/src/client/ConfidenceClient.ts @@ -1,5 +1,4 @@ import { Configuration, ResolveContext } from './Configuration'; -import { ConfidenceFlag, ResolvedFlag } from './ConfidenceFlag'; type ApplyRequest = { clientSecret: string; @@ -13,6 +12,13 @@ type ResolveRequest = { apply?: boolean; flags?: string[]; }; +export type ResolvedFlag = { + flag: string; + variant: string; + value?: T; + flagSchema?: ConfidenceFlagSchema; + reason: Configuration.ResolveReason; +}; type ResolveResponse = { resolvedFlags: ResolvedFlag[]; resolveToken: string; @@ -29,6 +35,12 @@ export type AppliedFlag = { flag: string; applyTime: string; }; +type ConfidenceBaseTypes = { boolSchema: {} } | { doubleSchema: {} } | { intSchema: {} } | { stringSchema: {} }; +type ConfidenceFlagSchema = { + schema: { + [key: string]: ConfidenceBaseTypes | { structSchema: ConfidenceFlagSchema }; + }; +}; export class ConfidenceClient { private readonly backendApplyEnabled: boolean; @@ -58,9 +70,10 @@ export class ConfidenceClient { body: JSON.stringify(payload), }); const responseBody: ResolveResponse = await response.json(); + return { flags: responseBody.resolvedFlags.reduce((acc, flag) => { - return { ...acc, [flag.flag]: new ConfidenceFlag(flag) }; + return { ...acc, [flag.flag]: resolvedFlagToFlag(flag) }; }, {}), resolveToken: responseBody.resolveToken, context, @@ -80,3 +93,49 @@ export class ConfidenceClient { }); } } + +function resolvedFlagToFlag(flag: ResolvedFlag): Configuration.Flag { + return { + flagName: flag.flag, + reason: flag.reason, + variant: flag.variant, + value: flag.value, + schema: parseSchema(flag.flagSchema), + }; +} + +function parseBaseType(obj: ConfidenceBaseTypes): Configuration.FlagSchema { + if ('boolSchema' in obj) { + return 'boolean'; + } + if ('doubleSchema' in obj) { + return 'number'; + } + if ('intSchema' in obj) { + return 'number'; + } + if ('stringSchema' in obj) { + return 'string'; + } + + throw new Error(`Confidence: cannot parse schema. unknown schema: ${JSON.stringify(obj)}`); +} +function parseSchema(schema: ConfidenceFlagSchema | undefined): Configuration.FlagSchema { + if (!schema) { + return {}; + } + + return Object.keys(schema.schema).reduce((acc: Record, key) => { + const obj = schema.schema[key]; + if ('structSchema' in obj) { + return { + ...acc, + [key]: parseSchema(obj.structSchema), + }; + } + return { + ...acc, + [key]: parseBaseType(obj), + }; + }, {}); +} diff --git a/packages/client-http/src/client/ConfidenceFlag.test.ts b/packages/client-http/src/client/ConfidenceFlag.test.ts deleted file mode 100644 index 4faa7bfe..00000000 --- a/packages/client-http/src/client/ConfidenceFlag.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ConfidenceFlag, ResolvedFlag } from './ConfidenceFlag'; -import { Configuration } from './Configuration'; -import ResolveReason = Configuration.ResolveReason; - -const resolvedFlag: ResolvedFlag = { - flag: 'test', - value: { - str: 'testVal', - }, - variant: 'treatment', - reason: ResolveReason.Match, - flagSchema: { - schema: { - str: { - stringSchema: {}, - }, - }, - }, -}; - -describe('ConfidenceFlag', () => { - it('should construct correctly', () => { - const flag = new ConfidenceFlag(resolvedFlag); - - expect(flag.value).toEqual(resolvedFlag.value); - expect(flag.reason).toEqual(resolvedFlag.reason); - expect(flag.variant).toEqual(resolvedFlag.variant); - expect(flag.flagName).toEqual(resolvedFlag.flag); - expect(flag.schema).toEqual({ str: 'string' }); - }); - - describe('parsing schema', () => { - it('should parse', () => { - const flag = new ConfidenceFlag({ - flag: 'test', - variant: 'treatment', - reason: ResolveReason.Match, - flagSchema: { - schema: { - str: { - stringSchema: {}, - }, - struct: { - structSchema: { - schema: { - str: { - stringSchema: {}, - }, - int: { - intSchema: {}, - }, - double: { - doubleSchema: {}, - }, - bool: { - boolSchema: {}, - }, - structStruct: { - structSchema: { - schema: { - bool: { - boolSchema: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }); - - expect(flag.schema).toEqual({ - str: 'string', - struct: { - str: 'string', - int: 'number', - double: 'number', - bool: 'boolean', - structStruct: { - bool: 'boolean', - }, - }, - }); - }); - }); - - describe('getValue', () => { - it('should return null value if the flag is not a match', () => { - const flag = new ConfidenceFlag({ ...resolvedFlag, reason: ResolveReason.Archived }); - - const val = flag.getValue('str'); - - expect(val!.value).toBeNull(); - expect(val!.match('')).toBeFalsy(); - }); - - it('should return null value if the flag no schema', () => { - const flag = new ConfidenceFlag({ ...resolvedFlag, flagSchema: undefined }); - - const val = flag.getValue('str'); - - expect(val).toBeNull(); - }); - - it('should return null value if the flag path is not found in the flag', () => { - const flag = new ConfidenceFlag({ ...resolvedFlag, flagSchema: undefined }); - - const val = flag.getValue('unknown'); - - expect(val).toBeNull(); - }); - - it('should return the correct value', () => { - const flag = new ConfidenceFlag(resolvedFlag); - - const val = flag.getValue('str'); - - expect(val!.value).toEqual('testVal'); - expect(val!.match('asdf')).toBeTruthy(); - }); - - it('should only match against the correct type', () => { - const flag = new ConfidenceFlag(resolvedFlag); - - const val = flag.getValue('str'); - - expect(val!.match('asdf')).toBeTruthy(); - expect(val!.match(false)).toBeFalsy(); - expect(val!.match(0)).toBeFalsy(); - expect(val!.match(null)).toBeFalsy(); - expect(val!.match(undefined)).toBeFalsy(); - expect(val!.match({})).toBeFalsy(); - }); - }); -}); diff --git a/packages/client-http/src/client/ConfidenceFlag.ts b/packages/client-http/src/client/ConfidenceFlag.ts deleted file mode 100644 index 7fc395a1..00000000 --- a/packages/client-http/src/client/ConfidenceFlag.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Configuration } from './Configuration'; - -type FlagSchema = - | 'number' - | 'boolean' - | 'string' - | { - [step: string]: FlagSchema; - }; -type ConfidenceBaseTypes = { boolSchema: {} } | { doubleSchema: {} } | { intSchema: {} } | { stringSchema: {} }; -type ConfidenceFlagSchema = { - schema: { - [key: string]: ConfidenceBaseTypes | { structSchema: ConfidenceFlagSchema }; - }; -}; - -function valueMatchesSchema(value: any, schema: FlagSchema): boolean { - if (value === null) { - return false; - } - - if (typeof schema !== 'object') { - return typeof value === schema; - } - - return Object.keys(value).every(key => valueMatchesSchema(value[key], schema[key])); -} -function parseBaseType(obj: ConfidenceBaseTypes): FlagSchema { - if ('boolSchema' in obj) { - return 'boolean'; - } - if ('doubleSchema' in obj) { - return 'number'; - } - if ('intSchema' in obj) { - return 'number'; - } - if ('stringSchema' in obj) { - return 'string'; - } - - throw new Error(`Confidence: cannot parse schema. unknown schema: ${JSON.stringify(obj)}`); -} -function parseSchema(schema: ConfidenceFlagSchema | undefined): FlagSchema { - if (!schema) { - return {}; - } - - return Object.keys(schema.schema).reduce((acc: Record, key) => { - const obj = schema.schema[key]; - if ('structSchema' in obj) { - return { - ...acc, - [key]: parseSchema(obj.structSchema), - }; - } - return { - ...acc, - [key]: parseBaseType(obj), - }; - }, {}); -} - -export type ResolvedFlag = { - flag: string; - variant: string; - value?: T; - flagSchema?: ConfidenceFlagSchema; - reason: Configuration.ResolveReason; -}; - -export class ConfidenceFlag implements Configuration.Flag { - readonly flagName: string; - readonly variant: string; - readonly value: unknown; - readonly schema: FlagSchema; - readonly reason: Configuration.ResolveReason; - - constructor(flag: ResolvedFlag) { - this.flagName = flag.flag; - this.reason = flag.reason; - this.variant = flag.variant; - this.value = flag.value; - this.schema = parseSchema(flag.flagSchema); - } - - getValue(...path: string[]): Configuration.FlagValue | null { - if (this.reason !== Configuration.ResolveReason.Match) { - return { - value: null, - match: () => false, - }; - } - - let value: any = this.value; - let schema: FlagSchema = this.schema; - for (const part of path) { - if (typeof schema !== 'object') { - return null; - } - value = value[part]; - schema = schema[part]; - if (schema === undefined) { - return null; - } - } - - return { - value, - match: (val): boolean => valueMatchesSchema(val, schema), - }; - } -} diff --git a/packages/client-http/src/client/Configuration.test.ts b/packages/client-http/src/client/Configuration.test.ts index 01139e41..35e58610 100644 --- a/packages/client-http/src/client/Configuration.test.ts +++ b/packages/client-http/src/client/Configuration.test.ts @@ -1,123 +1,55 @@ import { Configuration } from './Configuration'; -import { ConfidenceFlag } from './ConfidenceFlag'; -const fakeConfiguration: Configuration = { - flags: { - test: new ConfidenceFlag({ - flag: 'test', - variant: 'test', - value: { - bool: true, - str: 'base string', - double: 1.1, - int: 1, - obj: { - bool: true, - str: 'obj string', - double: 2.1, - int: 2, - obj: { - bool: true, - str: 'obj obj string', - double: 3.1, - int: 3, - }, - }, - }, - flagSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - double: { - doubleSchema: {}, - }, - int: { - intSchema: {}, +describe('Configuration', () => { + describe('Configuration.Flag.getFlagDetails', () => { + it('should get the value and the schema', () => { + const result = Configuration.Flag.getFlagDetails( + { + flagName: 'test', + schema: { + a: { + b: 'string', + }, }, - obj: { - structSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - double: { - doubleSchema: {}, - }, - int: { - intSchema: {}, - }, - obj: { - structSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - double: { - doubleSchema: {}, - }, - int: { - intSchema: {}, - }, - }, - }, - }, - }, + reason: Configuration.ResolveReason.Match, + value: { + a: { + b: 'hello world', }, }, + variant: 'control', }, - }, - reason: Configuration.ResolveReason.Match, - }), - emptyFlag: new ConfidenceFlag({ - flag: 'emptyFlag', - variant: '', - reason: Configuration.ResolveReason.NoSegmentMatch, - }), - }, - resolveToken: 'test-token', - context: {}, -}; + 'a', + 'b', + ); -describe('Configuration', () => { - it.each` - path | expectedValue - ${['bool']} | ${true} - ${['str']} | ${'base string'} - ${['double']} | ${1.1} - ${['int']} | ${1} - ${['obj', 'bool']} | ${true} - ${['obj', 'str']} | ${'obj string'} - ${['obj', 'double']} | ${2.1} - ${['obj', 'int']} | ${2} - ${['obj', 'obj', 'bool']} | ${true} - ${['obj', 'obj', 'str']} | ${'obj obj string'} - ${['obj', 'obj', 'double']} | ${3.1} - ${['obj', 'obj', 'int']} | ${3} - ${['obj', 'obj']} | ${{ bool: true, str: 'obj obj string', double: 3.1, int: 3 }} - ${['obj']} | ${{ bool: true, str: 'obj string', double: 2.1, int: 2, obj: { bool: true, str: 'obj obj string', double: 3.1, int: 3 } }} - `('should get "$expectedValue" for path $path', ({ path, expectedValue }) => { - expect(fakeConfiguration.flags.test.getValue(...path)?.value).toEqual(expectedValue); - }); - - describe('emptyFlag', () => { - it('should return a value of null', () => { - expect(fakeConfiguration.flags.emptyFlag.getValue('nothing')?.value).toEqual(null); + expect(result.value).toEqual('hello world'); + expect(result.schema).toEqual('string'); }); - }); - describe('parseError', () => { - it('should return null info', () => { - expect(fakeConfiguration.flags.test.getValue('404')).toEqual(null); + it('should throw an error when the path not traversable for the value and schema', () => { + expect(() => + Configuration.Flag.getFlagDetails( + { + flagName: 'test', + schema: { + a: { + b: 'string', + }, + }, + reason: Configuration.ResolveReason.Match, + value: { + a: { + b: 'hello world', + }, + }, + variant: 'control', + }, + 'a', + 'b', + 'c', + ), + ).toThrowError(); }); }); }); diff --git a/packages/client-http/src/client/Configuration.ts b/packages/client-http/src/client/Configuration.ts index 7fad6478..dcf2cd63 100644 --- a/packages/client-http/src/client/Configuration.ts +++ b/packages/client-http/src/client/Configuration.ts @@ -8,16 +8,53 @@ export namespace Configuration { NoTreatmentMatch = 'RESOLVE_REASON_NO_TREATMENT_MATCH', Archived = 'RESOLVE_REASON_FLAG_ARCHIVED', } - export interface FlagValue { - readonly value: T; - match(obj: S): this is FlagValue; + + export type FlagSchema = + | 'number' + | 'boolean' + | 'string' + | { + [step: string]: FlagSchema; + }; + + export interface Flag { + flagName: string; + reason: ResolveReason; + variant: string; + value: T; + schema: FlagSchema; } - export interface Flag { - readonly flagName: string; - readonly variant: string; - readonly reason: ResolveReason; - getValue(...path: string[]): FlagValue | null; + export namespace Flag { + export type Details = { value: T; schema: FlagSchema }; + export function valueMatchesSchema(value: any, schema: FlagSchema | null): boolean { + if (value === null || schema === null) { + return false; + } + + if (typeof schema !== 'object') { + return typeof value === schema; + } + + return Object.keys(value).every(key => valueMatchesSchema(value[key], schema[key])); + } + export function getFlagDetails(flag: Flag, ...path: string[]): Details { + let value: any = flag.value; + let schema: FlagSchema = flag.schema; + + for (const part of path) { + if (typeof schema !== 'object') { + throw new Error(`Parse Error. Cannot find path: ${path.join(',')}. In flag: ${JSON.stringify(flag)}`); + } + value = value[part]; + schema = schema[part]; + if (schema === undefined) { + throw new Error(`Parse Error. Cannot find path: ${path.join(',')}. In flag: ${JSON.stringify(flag)}`); + } + } + + return { value, schema }; + } } } export interface Configuration { diff --git a/packages/client-http/src/client/index.ts b/packages/client-http/src/client/index.ts index 2890f3e4..3cde1adc 100644 --- a/packages/client-http/src/client/index.ts +++ b/packages/client-http/src/client/index.ts @@ -1,3 +1,2 @@ export * from './Configuration'; export * from './ConfidenceClient'; -export * from './ConfidenceFlag'; diff --git a/packages/openfeature-server-provider/src/ConfidenceServerProvider.test.ts b/packages/openfeature-server-provider/src/ConfidenceServerProvider.test.ts index bab72661..31ea181e 100644 --- a/packages/openfeature-server-provider/src/ConfidenceServerProvider.test.ts +++ b/packages/openfeature-server-provider/src/ConfidenceServerProvider.test.ts @@ -1,5 +1,5 @@ import { ErrorCode, Logger, ProviderStatus } from '@openfeature/web-sdk'; -import { ConfidenceClient, Configuration, ConfidenceFlag } from '@spotify-confidence/client-http'; +import { ConfidenceClient, Configuration } from '@spotify-confidence/client-http'; import { ConfidenceServerProvider } from './ConfidenceServerProvider'; const mockApply = jest.fn(); @@ -20,8 +20,8 @@ const mockClient = { const dummyConfiguration: Configuration = { flags: { - ['flags/testFlag']: new ConfidenceFlag({ - flag: 'flags/testFlag', + ['flags/testFlag']: { + flagName: 'flags/testFlag', variant: 'control', value: { bool: true, @@ -36,56 +36,28 @@ const dummyConfiguration: Configuration = { }, }, reason: Configuration.ResolveReason.Match, - flagSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - int: { - intSchema: {}, - }, - dub: { - doubleSchema: {}, - }, - obj: { - structSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - int: { - intSchema: {}, - }, - dub: { - doubleSchema: {}, - }, - }, - }, - }, + schema: { + bool: 'boolean', + str: 'string', + int: 'number', + dub: 'number', + obj: { + bool: 'boolean', + str: 'string', + int: 'number', + dub: 'number', }, }, - }), - ['flags/anotherFlag']: new ConfidenceFlag({ - flag: 'flags/anotherFlag', + }, + ['flags/anotherFlag']: { + flagName: 'flags/anotherFlag', variant: 'control', value: { bool: true, }, reason: Configuration.ResolveReason.Match, - flagSchema: { - schema: { - bool: { - boolSchema: {}, - }, - }, - }, - }), + schema: { bool: 'boolean' }, + }, }, resolveToken: 'before-each', context: {}, diff --git a/packages/openfeature-server-provider/src/ConfidenceServerProvider.ts b/packages/openfeature-server-provider/src/ConfidenceServerProvider.ts index 42af8276..2005faf0 100644 --- a/packages/openfeature-server-provider/src/ConfidenceServerProvider.ts +++ b/packages/openfeature-server-provider/src/ConfidenceServerProvider.ts @@ -1,15 +1,15 @@ import { - ProviderMetadata, - ProviderStatus, + ErrorCode, EvaluationContext, - Logger, - ResolutionDetails, JsonValue, - ErrorCode, + Logger, Provider, + ProviderMetadata, + ProviderStatus, + ResolutionDetails, } from '@openfeature/js-sdk'; -import { ConfidenceClient, ResolveContext, ApplyManager, Configuration } from '@spotify-confidence/client-http'; +import { ApplyManager, ConfidenceClient, Configuration, ResolveContext } from '@spotify-confidence/client-http'; interface ConfidenceServerProviderOptions { apply?: { @@ -75,31 +75,33 @@ export class ConfidenceServerProvider implements Provider { }; } - const flagValue = flag.getValue(...pathParts); - if (flagValue === null) { + let flagDetails: Configuration.Flag.Details; + try { + flagDetails = Configuration.Flag.getFlagDetails(flag, ...pathParts); + } catch (e) { return { errorCode: 'PARSE_ERROR' as ErrorCode, value: defaultValue, reason: 'ERROR', }; } - - if (!flagValue.match(defaultValue)) { + if (flagDetails.value === null) { return { - errorCode: 'TYPE_MISMATCH' as ErrorCode, value: defaultValue, - reason: 'ERROR', + reason: flag.reason, }; } - if (flagValue.value === null) { + if (!Configuration.Flag.valueMatchesSchema(defaultValue, flagDetails.schema)) { return { + errorCode: 'TYPE_MISMATCH' as ErrorCode, value: defaultValue, - reason: flag.reason, + reason: 'ERROR', }; } + this.applyManager.apply(configuration.resolveToken, flagName); return { - value: flagValue.value, + value: flagDetails.value as T, reason: 'TARGETING_MATCH', variant: flag.variant, flagMetadata: { diff --git a/packages/openfeature-web-provider/src/ConfidenceWebProvider.test.ts b/packages/openfeature-web-provider/src/ConfidenceWebProvider.test.ts index 3cacddc8..3cc940a1 100644 --- a/packages/openfeature-web-provider/src/ConfidenceWebProvider.test.ts +++ b/packages/openfeature-web-provider/src/ConfidenceWebProvider.test.ts @@ -7,7 +7,7 @@ import { ProviderStatus, } from '@openfeature/web-sdk'; import { ConfidenceWebProvider } from './ConfidenceWebProvider'; -import { ConfidenceClient, ConfidenceFlag, Configuration, ResolveContext } from '@spotify-confidence/client-http'; +import { ConfidenceClient, Configuration, ResolveContext } from '@spotify-confidence/client-http'; const mockApply = jest.fn(); jest.mock('@spotify-confidence/client-http', () => { @@ -30,8 +30,8 @@ const dummyEvaluationContext: EvaluationContext = { targetingKey: 'test' }; const dummyConfiguration: Configuration = { flags: { - ['flags/testFlag']: new ConfidenceFlag({ - flag: 'flags/testFlag', + ['flags/testFlag']: { + flagName: 'flags/testFlag', variant: 'control', value: { bool: true, @@ -46,56 +46,30 @@ const dummyConfiguration: Configuration = { }, }, reason: Configuration.ResolveReason.Match, - flagSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - int: { - intSchema: {}, - }, - dub: { - doubleSchema: {}, - }, - obj: { - structSchema: { - schema: { - bool: { - boolSchema: {}, - }, - str: { - stringSchema: {}, - }, - int: { - intSchema: {}, - }, - dub: { - doubleSchema: {}, - }, - }, - }, - }, + schema: { + bool: 'boolean', + str: 'string', + int: 'number', + dub: 'number', + obj: { + bool: 'boolean', + str: 'string', + int: 'number', + dub: 'number', }, }, - }), - ['flags/anotherFlag']: new ConfidenceFlag({ - flag: 'flags/anotherFlag', + }, + ['flags/anotherFlag']: { + flagName: 'flags/anotherFlag', variant: 'control', value: { bool: true, }, reason: Configuration.ResolveReason.Match, - flagSchema: { - schema: { - bool: { - boolSchema: {}, - }, - }, + schema: { + bool: 'boolean', }, - }), + }, }, resolveToken: 'before-each', context: dummyContext, diff --git a/packages/openfeature-web-provider/src/ConfidenceWebProvider.ts b/packages/openfeature-web-provider/src/ConfidenceWebProvider.ts index bf61fc5a..258a84f6 100644 --- a/packages/openfeature-web-provider/src/ConfidenceWebProvider.ts +++ b/packages/openfeature-web-provider/src/ConfidenceWebProvider.ts @@ -122,8 +122,10 @@ export class ConfidenceWebProvider implements Provider { }; } - const flagValue = flag.getValue(...pathParts); - if (flagValue === null) { + let flagDetails: Configuration.Flag.Details; + try { + flagDetails = Configuration.Flag.getFlagDetails(flag, ...pathParts); + } catch (e) { logger.warn('Value with path "%s" was not found in flag "%s"', pathParts.join('.'), flagName); return { errorCode: ErrorCode.PARSE_ERROR, @@ -131,26 +133,25 @@ export class ConfidenceWebProvider implements Provider { reason: 'ERROR', }; } - - if (!flagValue.match(defaultValue)) { - logger.warn('Value for "%s" is of incorrect type', flagKey); + if (flagDetails.value === null) { return { - errorCode: ErrorCode.TYPE_MISMATCH, value: defaultValue, - reason: 'ERROR', + reason: flag.reason, }; } - if (flagValue.value === null) { - logger.info('Value for "%s" is default', flagKey); + if (!Configuration.Flag.valueMatchesSchema(defaultValue, flagDetails.schema)) { + logger.warn('Value for "%s" is of incorrect type', flagKey); return { + errorCode: ErrorCode.TYPE_MISMATCH, value: defaultValue, - reason: flag.reason, + reason: 'ERROR', }; } + this.applyManager.apply(this.configuration.resolveToken, flagName); logger.info('Value for "%s" successfully evaluated', flagKey); return { - value: flagValue.value, + value: flagDetails.value as T, reason: 'TARGETING_MATCH', variant: flag.variant, flagMetadata: { diff --git a/packages/openfeature-web-provider/src/factory.ts b/packages/openfeature-web-provider/src/factory.ts index 9989e0f2..8f4a00d6 100644 --- a/packages/openfeature-web-provider/src/factory.ts +++ b/packages/openfeature-web-provider/src/factory.ts @@ -1,8 +1,7 @@ -import { Provider } from '@openfeature/web-sdk'; import { ConfidenceWebProvider } from './ConfidenceWebProvider'; import { ConfidenceClient } from '@spotify-confidence/client-http'; -type ConfidenceWebProviderFactoryOptions = { +export type ConfidenceWebProviderFactoryOptions = { region: 'eu' | 'us'; fetchImplementation: typeof fetch; clientSecret: string; @@ -12,7 +11,7 @@ type ConfidenceWebProviderFactoryOptions = { }; }; -export function createConfidenceWebProvider(options: ConfidenceWebProviderFactoryOptions): Provider { +export function createConfidenceWebProvider(options: ConfidenceWebProviderFactoryOptions): ConfidenceWebProvider { const confidenceClient = new ConfidenceClient({ ...options, apply: !options.apply,