diff --git a/libs/providers/flagd/README.md b/libs/providers/flagd/README.md index a7922ea55..d33d59d5a 100644 --- a/libs/providers/flagd/README.md +++ b/libs/providers/flagd/README.md @@ -1,4 +1,4 @@ -# Server-side flagd Provider +# Server-Side flagd Provider Flagd is a simple daemon for evaluating feature flags. It is designed to conform to OpenFeature schema for flag definitions. @@ -21,19 +21,19 @@ $ npm install @openfeature/js-sdk The `FlagdProvider` supports multiple configuration options that determine now the SDK communicates with flagd. Options can be defined in the constructor or as environment variables, with constructor options having the highest precedence. -### Available options +### Available Options -| Option name | Environment variable name | Type | Default | Values | -| --------------------- | ------------------------------- | ------- | --------- | ------------- | -| host | FLAGD_HOST | string | localhost | | -| port | FLAGD_PORT | number | 8013 | | -| tls | FLAGD_TLS | boolean | false | | -| socketPath | FLAGD_SOCKET_PATH | string | - | | -| cache | FLAGD_CACHE | string | lru | lru,disabled | -| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | | -| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | | +| Option name | Environment variable name | Type | Default | Values | +| --------------------- | ------------------------------ | ------- | --------- | ------------ | +| host | FLAGD_HOST | string | localhost | | +| port | FLAGD_PORT | number | 8013 | | +| tls | FLAGD_TLS | boolean | false | | +| socketPath | FLAGD_SOCKET_PATH | string | - | | +| cache | FLAGD_CACHE | string | lru | lru,disabled | +| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | | +| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | | -### Example using TCP +### Example Using TCP ``` OpenFeature.setProvider(new FlagdProvider({ @@ -42,7 +42,7 @@ Options can be defined in the constructor or as environment variables, with cons })) ``` -### Example using a Unix socket +### Example Using a Unix Socket ``` OpenFeature.setProvider(new FlagdProvider({ @@ -50,7 +50,7 @@ Options can be defined in the constructor or as environment variables, with cons })) ``` -### Supported events +### Supported Events The flagd provider emits `PROVIDER_READY`, `PROVIDER_ERROR` and `PROVIDER_CONFIGURATION_CHANGED` events. @@ -62,12 +62,18 @@ The flagd provider emits `PROVIDER_READY`, `PROVIDER_ERROR` and `PROVIDER_CONFIG For general information on events, see the [official documentation](https://openfeature.dev/docs/reference/concepts/events). +### Flag Metadata + +| Field | Type | Value | +| ------- | ------ | ------------------------------------------------- | +| `scope` | string | "selector" set for the associated source in flagd | + ## Building Run `nx package providers-flagd` to build the library. > NOTE: [Buf](https://docs.buf.build/installation) must be installed to build locally. -## Running unit tests +## Running Unit Tests Run `nx test providers-flagd` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/providers/flagd/src/lib/flagd-provider.spec.ts b/libs/providers/flagd/src/lib/flagd-provider.spec.ts index 1cd88da6d..9ed211c17 100644 --- a/libs/providers/flagd/src/lib/flagd-provider.spec.ts +++ b/libs/providers/flagd/src/lib/flagd-provider.spec.ts @@ -3,6 +3,7 @@ import { Client, ErrorCode, EvaluationContext, + FlagMetadata, OpenFeature, ProviderEvents, ProviderStatus, @@ -25,7 +26,7 @@ import { } from '../proto/ts/schema/v1/schema'; import { EVENT_CONFIGURATION_CHANGE, EVENT_PROVIDER_READY } from './constants'; import { FlagdProvider } from './flagd-provider'; -import { FlagChangeMessage, GRPCService } from './service/grpc/service'; +import { FlagChangeMessage, GRPCService } from './service/grpc/grpc-service'; const REASON = StandardResolutionReasons.STATIC; const ERROR_REASON = StandardResolutionReasons.ERROR; @@ -54,6 +55,12 @@ const TEST_CONTEXT_KEY = 'context-key'; const TEST_CONTEXT_VALUE = 'context-value'; const TEST_CONTEXT = { [TEST_CONTEXT_KEY]: TEST_CONTEXT_VALUE }; +const METADATA: FlagMetadata = { + metadataKey1: 'value1', + metadataKey2: 2, + metadataKey3: true, +}; + describe(FlagdProvider.name, () => { beforeEach(() => { jest.clearAllMocks(); @@ -80,7 +87,7 @@ describe(FlagdProvider.name, () => { value: BOOLEAN_VALUE, variant: BOOLEAN_VARIANT, reason: REASON, - metadata: {} + metadata: METADATA, }); }), resolveString: jest.fn((request: ResolveStringRequest, callback: (error: ServiceError | null, response: ResolveStringResponse) => void) => { @@ -88,7 +95,7 @@ describe(FlagdProvider.name, () => { value: STRING_VALUE, variant: STRING_VARIANT, reason: REASON, - metadata: {} + metadata: METADATA, }); }), resolveFloat: jest.fn((request: ResolveFloatRequest, callback: (error: ServiceError | null, response: ResolveFloatResponse) => void) => { @@ -96,7 +103,7 @@ describe(FlagdProvider.name, () => { value: NUMBER_VALUE, variant: NUMBER_VARIANT, reason: REASON, - metadata: {} + metadata: METADATA, }); }), resolveInt: jest.fn((): UnaryCall => { @@ -107,7 +114,7 @@ describe(FlagdProvider.name, () => { value: OBJECT_VALUE, variant: OBJECT_VARIANT, reason: REASON, - metadata: {} + metadata: METADATA, }); }), } as unknown as ServiceClient; @@ -130,6 +137,7 @@ describe(FlagdProvider.name, () => { expect(val.value).toEqual(BOOLEAN_VALUE); expect(val.variant).toEqual(BOOLEAN_VARIANT); expect(val.reason).toEqual(REASON); + expect(val.flagMetadata).toEqual(METADATA); }); }); @@ -143,6 +151,7 @@ describe(FlagdProvider.name, () => { expect(val.value).toEqual(STRING_VALUE); expect(val.variant).toEqual(STRING_VARIANT); expect(val.reason).toEqual(REASON); + expect(val.flagMetadata).toEqual(METADATA); }); }); @@ -156,6 +165,7 @@ describe(FlagdProvider.name, () => { expect(val.value).toEqual(NUMBER_VALUE); expect(val.variant).toEqual(NUMBER_VARIANT); expect(val.reason).toEqual(REASON); + expect(val.flagMetadata).toEqual(METADATA); }); }); @@ -169,6 +179,7 @@ describe(FlagdProvider.name, () => { expect(val.value).toEqual({ [OBJECT_INNER_KEY]: OBJECT_INNER_VALUE }); expect(val.variant).toEqual(OBJECT_VARIANT); expect(val.reason).toEqual(REASON); + expect(val.flagMetadata).toEqual(METADATA); }); }); @@ -184,6 +195,7 @@ describe(FlagdProvider.name, () => { expect(val.value).toEqual(BOOLEAN_VALUE); expect(val.variant).toEqual(BOOLEAN_VARIANT); expect(val.reason).toEqual(REASON); + expect(val.flagMetadata).toEqual(METADATA); }); }); }); @@ -206,22 +218,16 @@ describe(FlagdProvider.name, () => { resolveBoolean: jest.fn((request: ResolveBooleanRequest, callback: (error: ServiceError | null, response: ResolveBooleanResponse) => void) => { callback(null, { variant: BOOLEAN_VARIANT, - reason: REASON, - metadata: {} } as ResolveBooleanResponse); }), resolveString: jest.fn((request: ResolveStringRequest, callback: (error: ServiceError | null, response: ResolveStringResponse) => void) => { callback(null, { variant: STRING_VARIANT, - reason: REASON, - metadata: {} } as ResolveStringResponse); }), resolveFloat: jest.fn((request: ResolveFloatRequest, callback: (error: ServiceError | null, response: ResolveFloatResponse) => void) => { callback(null, { variant: NUMBER_VARIANT, - reason: REASON, - metadata: {} } as ResolveFloatResponse); }), resolveInt: jest.fn((): UnaryCall => { @@ -230,8 +236,6 @@ describe(FlagdProvider.name, () => { resolveObject: jest.fn((request: ResolveObjectRequest, callback: (error: ServiceError | null, response: ResolveObjectResponse) => void) => { callback(null, { variant: OBJECT_VARIANT, - reason: REASON, - metadata: {} } as ResolveObjectResponse); }), } as unknown as ServiceClient; @@ -245,54 +249,46 @@ describe(FlagdProvider.name, () => { }); describe(FlagdProvider.prototype.resolveBooleanEvaluation.name, () => { - it(`should call ${ServiceClient.prototype.resolveBoolean} with key and context and return details`, async () => { + it(`should call ${ServiceClient.prototype.resolveBoolean} with key and context and return falsy value`, async () => { const val = await client.getBooleanDetails(BOOLEAN_KEY, false, TEST_CONTEXT); expect(basicServiceClientMock.resolveBoolean).toHaveBeenCalledWith({ flagKey: BOOLEAN_KEY, context: TEST_CONTEXT, }, expect.any(Function)); expect(val.value).toEqual(false); - expect(val.variant).toEqual(BOOLEAN_VARIANT); - expect(val.reason).toEqual(REASON); }); }); describe(FlagdProvider.prototype.resolveStringEvaluation.name, () => { - it(`should call ${ServiceClient.prototype.resolveString} with key and context and return details`, async () => { + it(`should call ${ServiceClient.prototype.resolveString} with key and context and return falsy value`, async () => { const val = await client.getStringDetails(STRING_KEY, '', TEST_CONTEXT); expect(basicServiceClientMock.resolveString).toHaveBeenCalledWith({ flagKey: STRING_KEY, context: TEST_CONTEXT, }, expect.any(Function)); expect(val.value).toEqual(''); - expect(val.variant).toEqual(STRING_VARIANT); - expect(val.reason).toEqual(REASON); }); }); describe(FlagdProvider.prototype.resolveNumberEvaluation.name, () => { - it(`should call ${ServiceClient.prototype.resolveFloat} with key and context and return details`, async () => { + it(`should call ${ServiceClient.prototype.resolveFloat} with key and context and return falsy value`, async () => { const val = await client.getNumberDetails(NUMBER_KEY, 0, TEST_CONTEXT); expect(basicServiceClientMock.resolveFloat).toHaveBeenCalledWith({ flagKey: NUMBER_KEY, context: TEST_CONTEXT, }, expect.any(Function)); expect(val.value).toEqual(0); - expect(val.variant).toEqual(NUMBER_VARIANT); - expect(val.reason).toEqual(REASON); }); }); describe(FlagdProvider.prototype.resolveObjectEvaluation.name, () => { - it(`should call ${ServiceClient.prototype.resolveObject} with key and context and return details`, async () => { + it(`should call ${ServiceClient.prototype.resolveObject} with key and context and return falsy value`, async () => { const val = await client.getObjectDetails(OBJECT_KEY, {}, TEST_CONTEXT); expect(basicServiceClientMock.resolveObject).toHaveBeenCalledWith({ flagKey: OBJECT_KEY, context: TEST_CONTEXT, }, expect.any(Function)); expect(val.value).toEqual({}); - expect(val.variant).toEqual(OBJECT_VARIANT); - expect(val.reason).toEqual(REASON); }); }); }); diff --git a/libs/providers/flagd/src/lib/flagd-provider.ts b/libs/providers/flagd/src/lib/flagd-provider.ts index c6547b4b1..e2e910003 100644 --- a/libs/providers/flagd/src/lib/flagd-provider.ts +++ b/libs/providers/flagd/src/lib/flagd-provider.ts @@ -9,7 +9,7 @@ import { ResolutionDetails, } from '@openfeature/js-sdk'; import { FlagdProviderOptions, getConfig } from './configuration'; -import { GRPCService } from './service/grpc/service'; +import { GRPCService } from './service/grpc/grpc-service'; import { Service } from './service/service'; export class FlagdProvider implements Provider { diff --git a/libs/providers/flagd/src/lib/service/grpc/service.ts b/libs/providers/flagd/src/lib/service/grpc/grpc-service.ts similarity index 99% rename from libs/providers/flagd/src/lib/service/grpc/service.ts rename to libs/providers/flagd/src/lib/service/grpc/grpc-service.ts index 9364bb338..08ab9beba 100644 --- a/libs/providers/flagd/src/lib/service/grpc/service.ts +++ b/libs/providers/flagd/src/lib/service/grpc/grpc-service.ts @@ -280,11 +280,12 @@ export class GRPCService implements Service { .call(this._client, { flagKey, context }) .then((resolved) => resolved, this.onRejected); - const resolved = { + const resolved: ResolutionDetails = { // invoke the parser method if passed value: parser.call(this, response.value as T), reason: response.reason, variant: response.variant, + flagMetadata: response.metadata, }; logger.debug(