Skip to content

Commit 383f4f3

Browse files
authored
feat!: use native headers, optional query params (open-feature#1003)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 334fd84 commit 383f4f3

File tree

14 files changed

+188
-174
lines changed

14 files changed

+188
-174
lines changed

libs/providers/ofrep-web/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"current-version": "echo $npm_package_version"
1212
},
1313
"peerDependencies": {
14-
"@openfeature/web-sdk": ">=0.4.0",
15-
"@openfeature/ofrep-core": "^0.1.5"
14+
"@openfeature/web-sdk": ">=0.4.0"
1615
}
1716
}

libs/providers/ofrep-web/project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
"input": "./libs/providers/ofrep-web",
6969
"output": "./"
7070
}
71-
]
71+
],
72+
"updateBuildableProjectDepsInPackageJson": true
7273
}
7374
}
7475
},

libs/providers/ofrep-web/src/lib/ofrep-web-provider.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
OFREPApiTooManyRequestsError,
1414
OFREPApiUnauthorizedError,
1515
OFREPForbiddenError,
16-
RequestOptions,
1716
handleEvaluationError,
1817
isEvaluationFailureResponse,
1918
isEvaluationSuccessResponse,
@@ -66,7 +65,7 @@ export class OFREPWebProvider implements Provider {
6665
this._options = options;
6766
this._logger = logger;
6867
this._etag = null;
69-
this._ofrepAPI = new OFREPApi(this._options.baseUrl, this._options.fetchImplementation);
68+
this._ofrepAPI = new OFREPApi(this._options, this._options.fetchImplementation);
7069
this._pollingInterval = this._options.pollInterval ?? this.DEFAULT_POLL_INTERVAL;
7170
}
7271

@@ -189,14 +188,8 @@ export class OFREPWebProvider implements Provider {
189188
const evalReq: EvaluationRequest = {
190189
context,
191190
};
192-
const options: RequestOptions = {
193-
headers: new Headers({
194-
'Content-Type': 'application/json',
195-
}),
196-
...(this._etag !== null ? { headers: { 'If-None-Match': this._etag } } : {}),
197-
};
198191

199-
const response = await this._ofrepAPI.postBulkEvaluateFlags(evalReq, options);
192+
const response = await this._ofrepAPI.postBulkEvaluateFlags(evalReq, this._etag);
200193
if (response.httpStatus === 304) {
201194
// nothing has changed since last time, we are doing nothing
202195
return { status: BulkEvaluationStatus.SUCCESS_NO_CHANGES, flags: [] };

libs/providers/ofrep/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"current-version": "echo $npm_package_version"
1212
},
1313
"peerDependencies": {
14-
"@openfeature/server-sdk": "^1.6.0",
15-
"@openfeature/ofrep-core": "^0.1.4"
14+
"@openfeature/server-sdk": "^1.6.0"
1615
}
1716
}

libs/providers/ofrep/project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
"input": "./libs/providers/ofrep",
6969
"output": "./"
7070
}
71-
]
71+
],
72+
"updateBuildableProjectDepsInPackageJson": true
7273
}
7374
}
7475
},

libs/providers/ofrep/src/lib/ofrep-provider.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('OFREPProvider should', () => {
5454
OFREPApiUnauthorizedError,
5555
);
5656

57-
const providerWithAuth = new OFREPProvider({ ...defaultOptions, headers: { Authorization: 'secret' } });
57+
const providerWithAuth = new OFREPProvider({ ...defaultOptions, headers: [['Authorization', 'secret']] });
5858
const flag = await providerWithAuth.resolveBooleanEvaluation('my-flag', false, { expectedAuthHeader: 'secret' });
5959
expect(flag.value).toEqual(true);
6060
});
@@ -154,7 +154,7 @@ describe('OFREPProvider should', () => {
154154

155155
const providerWithAuth = new OFREPProvider({
156156
...defaultOptions,
157-
headersFactory: () => [['Authorization', 'secret']],
157+
headersFactory: () => Promise.resolve([['Authorization', 'secret']]),
158158
});
159159
const flag = await providerWithAuth.resolveBooleanEvaluation('my-flag', false, { expectedAuthHeader: 'secret' });
160160
expect(flag.value).toEqual(true);

libs/providers/ofrep/src/lib/ofrep-provider.ts

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,16 @@
1-
import {
2-
ErrorCode,
3-
EvaluationContext,
4-
JsonValue,
5-
Provider,
6-
ResolutionDetails,
7-
StandardResolutionReasons,
8-
TypeMismatchError,
9-
} from '@openfeature/server-sdk';
1+
import { GeneralError } from '@openfeature/core';
102
import {
113
EvaluationFlagValue,
12-
handleEvaluationError,
13-
HttpHeaders,
14-
mergeHeaders,
154
OFREPApi,
165
OFREPApiEvaluationResult,
176
OFREPApiTooManyRequestsError,
187
OFREPProviderBaseOptions,
19-
RequestOptions,
20-
toRequestOptions,
8+
handleEvaluationError,
219
toResolutionDetails,
2210
} from '@openfeature/ofrep-core';
23-
import { GeneralError } from '@openfeature/core';
11+
import { EvaluationContext, JsonValue, Provider, ResolutionDetails, TypeMismatchError } from '@openfeature/server-sdk';
2412

25-
export type OFREPProviderOptions = Omit<OFREPProviderBaseOptions, 'headersFactory'> & {
26-
headersFactory?: () => Promise<HttpHeaders> | HttpHeaders;
27-
};
13+
export type OFREPProviderOptions = OFREPProviderBaseOptions;
2814

2915
export class OFREPProvider implements Provider {
3016
private notBefore: Date | null = null;
@@ -43,7 +29,7 @@ export class OFREPProvider implements Provider {
4329
throw new Error(`The given OFREP URL "${this.options.baseUrl}" is not a valid URL.`);
4430
}
4531

46-
this.ofrepApi = new OFREPApi(options.baseUrl, options.fetchImplementation);
32+
this.ofrepApi = new OFREPApi(options, options.fetchImplementation);
4733
}
4834

4935
public async resolveBooleanEvaluation(
@@ -91,7 +77,7 @@ export class OFREPProvider implements Provider {
9177
}
9278

9379
try {
94-
const result = await this.ofrepApi.postEvaluateFlags(flagKey, { context }, await this.baseRequestOptions());
80+
const result = await this.ofrepApi.postEvaluateFlag(flagKey, { context });
9581
return this.toResolutionDetails(result, defaultValue);
9682
} catch (error) {
9783
if (error instanceof OFREPApiTooManyRequestsError) {
@@ -115,12 +101,4 @@ export class OFREPProvider implements Provider {
115101

116102
return toResolutionDetails(result.value);
117103
}
118-
119-
private async baseRequestOptions(): Promise<RequestOptions> {
120-
const { headers, headersFactory, ...restOptions } = this.options;
121-
return {
122-
...toRequestOptions(restOptions),
123-
headers: mergeHeaders(headers, await headersFactory?.()),
124-
};
125-
}
126104
}

libs/shared/ofrep-core/src/lib/api/ofrep-api.spec.ts

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('OFREPApi', () => {
2525
});
2626
beforeEach(() => {
2727
jest.useFakeTimers();
28-
api = new OFREPApi('https://localhost:8080');
28+
api = new OFREPApi({ baseUrl: 'https://localhost:8080' });
2929
});
3030
afterEach(() => {
3131
jest.runOnlyPendingTimers();
@@ -38,31 +38,31 @@ describe('OFREPApi', () => {
3838

3939
describe('postEvaluateFlags should', () => {
4040
it('throw OFREPApiFetchError on network error', async () => {
41-
await expect(() => api.postEvaluateFlags('my-flag', { context: { errors: { network: true } } })).rejects.toThrow(
41+
await expect(() => api.postEvaluateFlag('my-flag', { context: { errors: { network: true } } })).rejects.toThrow(
4242
OFREPApiFetchError,
4343
);
4444
});
4545

4646
it('throw OFREPApiUnexpectedResponseError on any error code without EvaluationFailureResponse body', async () => {
4747
await expect(() =>
48-
api.postEvaluateFlags('my-flag', { context: { errors: { generic400: true } } }),
48+
api.postEvaluateFlag('my-flag', { context: { errors: { generic400: true } } }),
4949
).rejects.toThrow(OFREPApiUnexpectedResponseError);
5050
});
5151

5252
it('throw OFREPForbiddenError on 401 response', async () => {
53-
await expect(() => api.postEvaluateFlags('my-flag', { context: { errors: { 401: true } } })).rejects.toThrow(
53+
await expect(() => api.postEvaluateFlag('my-flag', { context: { errors: { 401: true } } })).rejects.toThrow(
5454
OFREPApiUnauthorizedError,
5555
);
5656
});
5757

5858
it('throw OFREPForbiddenError on 403 response', async () => {
59-
await expect(() => api.postEvaluateFlags('my-flag', { context: { errors: { 403: true } } })).rejects.toThrow(
59+
await expect(() => api.postEvaluateFlag('my-flag', { context: { errors: { 403: true } } })).rejects.toThrow(
6060
OFREPForbiddenError,
6161
);
6262
});
6363

6464
it('throw OFREPApiTooManyRequestsError on 429 response', async () => {
65-
await expect(() => api.postEvaluateFlags('my-flag', { context: { errors: { 429: true } } })).rejects.toThrow(
65+
await expect(() => api.postEvaluateFlag('my-flag', { context: { errors: { 429: true } } })).rejects.toThrow(
6666
OFREPApiTooManyRequestsError,
6767
);
6868
});
@@ -71,7 +71,7 @@ describe('OFREPApi', () => {
7171
jest.setSystemTime(new Date('2018-01-27'));
7272

7373
try {
74-
await api.postEvaluateFlags('my-flag', { context: { errors: { 429: true } } });
74+
await api.postEvaluateFlag('my-flag', { context: { errors: { 429: true } } });
7575
} catch (error) {
7676
if (!(error instanceof OFREPApiTooManyRequestsError)) {
7777
throw new Error('Expected OFREPApiTooManyRequestsError');
@@ -86,7 +86,7 @@ describe('OFREPApi', () => {
8686
jest.setSystemTime(new Date('2018-01-27'));
8787

8888
try {
89-
await api.postEvaluateFlags('my-flag', { context: { errors: { 429: 'Sat, 27 Jan 2018 07:28:00 GMT' } } });
89+
await api.postEvaluateFlag('my-flag', { context: { errors: { 429: 'Sat, 27 Jan 2018 07:28:00 GMT' } } });
9090
} catch (error) {
9191
if (!(error instanceof OFREPApiTooManyRequestsError)) {
9292
throw new Error('Expected OFREPApiTooManyRequestsError');
@@ -101,7 +101,7 @@ describe('OFREPApi', () => {
101101
jest.setSystemTime(new Date('2018-01-27'));
102102

103103
try {
104-
await api.postEvaluateFlags('my-flag', { context: { errors: { 429: 'abcdefg' } } });
104+
await api.postEvaluateFlag('my-flag', { context: { errors: { 429: 'abcdefg' } } });
105105
} catch (error) {
106106
if (!(error instanceof OFREPApiTooManyRequestsError)) {
107107
throw new Error('Expected OFREPApiTooManyRequestsError');
@@ -113,12 +113,12 @@ describe('OFREPApi', () => {
113113
});
114114

115115
it('send empty request body if context is not given', async () => {
116-
const result = await api.postEvaluateFlags('my-flag');
116+
const result = await api.postEvaluateFlag('my-flag');
117117
expect(result.httpStatus).toEqual(200);
118118
});
119119

120120
it('send evaluation context in request body', async () => {
121-
const result = await api.postEvaluateFlags('context-in-metadata', {
121+
const result = await api.postEvaluateFlag('context-in-metadata', {
122122
context: {
123123
targetingKey: 'user-1',
124124
key1: 'value1',
@@ -138,12 +138,12 @@ describe('OFREPApi', () => {
138138
});
139139

140140
it('return HTTP status in result', async () => {
141-
const result = await api.postEvaluateFlags('my-flag');
141+
const result = await api.postEvaluateFlag('my-flag');
142142
expect(result.httpStatus).toEqual(200);
143143
});
144144

145145
it('return EvaluationFailureResponse response as value on HTTP 400', async () => {
146-
const result = await api.postEvaluateFlags('my-flag', { context: { errors: { notFound: true } } });
146+
const result = await api.postEvaluateFlag('my-flag', { context: { errors: { notFound: true } } });
147147
if (result.httpStatus !== 404) {
148148
throw new Error('Received unexpected HTTP status');
149149
}
@@ -155,7 +155,7 @@ describe('OFREPApi', () => {
155155
});
156156

157157
it('return EvaluationFailureResponse response as value on HTTP 400', async () => {
158-
const result = await api.postEvaluateFlags('my-flag', { context: { errors: { notFound: true } } });
158+
const result = await api.postEvaluateFlag('my-flag', { context: { errors: { notFound: true } } });
159159
if (result.httpStatus !== 404) {
160160
throw new Error('Received unexpected HTTP status');
161161
}
@@ -167,7 +167,7 @@ describe('OFREPApi', () => {
167167
});
168168

169169
it('determine value type based on HTTP status', async () => {
170-
const result = await api.postEvaluateFlags('my-flag');
170+
const result = await api.postEvaluateFlag('my-flag');
171171
expect(result.httpStatus).toEqual(200);
172172

173173
// This is to check if the value type is determined by http status code
@@ -179,7 +179,7 @@ describe('OFREPApi', () => {
179179
});
180180

181181
it('return EvaluationSuccessResponse response as value on successful evaluation', async () => {
182-
const result = await api.postEvaluateFlags('my-flag', { context: { targetingKey: 'user' } });
182+
const result = await api.postEvaluateFlag('my-flag', { context: { targetingKey: 'user' } });
183183
expect(result.httpStatus).toEqual(200);
184184
expect(result.value).toEqual({
185185
key: 'my-flag',
@@ -193,6 +193,23 @@ describe('OFREPApi', () => {
193193
},
194194
} satisfies EvaluationSuccessResponse);
195195
});
196+
197+
it('send query params with request', async () => {
198+
api = new OFREPApi({ baseUrl: 'https://localhost:8080', query: new URLSearchParams({ scope: '123' }) });
199+
const result = await api.postEvaluateFlag('my-flag', { context: { targetingKey: 'user' } });
200+
expect(result.httpStatus).toEqual(200);
201+
expect(result.value).toEqual({
202+
key: 'my-flag',
203+
reason: EvaluationSuccessReason.TargetingMatch,
204+
value: true,
205+
variant: 'scoped',
206+
metadata: {
207+
context: {
208+
targetingKey: 'user',
209+
},
210+
},
211+
} satisfies EvaluationSuccessResponse);
212+
});
196213
});
197214

198215
describe('postBulkEvaluateFlags should', () => {
@@ -302,11 +319,26 @@ describe('OFREPApi', () => {
302319
});
303320

304321
it('return BulkEvaluationNotModified response as value on 304', async () => {
305-
const result = await api.postBulkEvaluateFlags(undefined, { headers: [['If-None-Match', '1234']] });
322+
api = new OFREPApi({ baseUrl: 'https://localhost:8080', headers: [['If-None-Match', '1234']] });
323+
const result = await api.postBulkEvaluateFlags(undefined);
306324
expect(result.httpStatus).toEqual(304);
307325
expect(result.value).not.toBeDefined();
308326
});
309327

328+
it('send query params with request', async () => {
329+
api = new OFREPApi({ baseUrl: 'https://localhost:8080', query: new URLSearchParams({ scope: '123' }) });
330+
const result = await api.postBulkEvaluateFlags();
331+
expect(result.httpStatus).toEqual(200);
332+
expect(result.value).toEqual({
333+
flags: [
334+
{
335+
key: 'other-flag',
336+
value: true,
337+
},
338+
],
339+
});
340+
});
341+
310342
it('return BulkEvaluationSuccessResponse response as value on successful evaluation', async () => {
311343
const result = await api.postBulkEvaluateFlags();
312344
expect(result.httpStatus).toEqual(200);

0 commit comments

Comments
 (0)