From e07db95587f0b0e9e575ea0009c880b25ed10b8c Mon Sep 17 00:00:00 2001 From: Olivier Laviale Date: Wed, 4 Feb 2026 11:10:21 +0100 Subject: [PATCH] Add customHeaders option --- .../GOFeatureFlag/controller/goff_api.swift | 5 ++++ Sources/GOFeatureFlag/options.swift | 7 +++++ Sources/GOFeatureFlag/provider.swift | 2 +- Tests/GOFeatureFlagTests/goff_api_tests.swift | 25 ++++++++++++++++ Tests/GOFeatureFlagTests/provider_tests.swift | 29 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Sources/GOFeatureFlag/controller/goff_api.swift b/Sources/GOFeatureFlag/controller/goff_api.swift index 12ebe37..151324e 100644 --- a/Sources/GOFeatureFlag/controller/goff_api.swift +++ b/Sources/GOFeatureFlag/controller/goff_api.swift @@ -44,6 +44,11 @@ class GoFeatureFlagAPI { "application/json", forHTTPHeaderField: "Content-Type" ) + if let headers = self.options.customHeaders { + for (key, value) in headers { + request.SetValue(value, forHTTPHeaderField: key) + } + } if let apiKey = self.options.apiKey { request.setValue("Bearer \(apiKey)", forHTTPHeaderField:"Authorization") } diff --git a/Sources/GOFeatureFlag/options.swift b/Sources/GOFeatureFlag/options.swift index e271353..f1993f4 100644 --- a/Sources/GOFeatureFlag/options.swift +++ b/Sources/GOFeatureFlag/options.swift @@ -20,6 +20,11 @@ public struct GoFeatureFlagProviderOptions { * Default: null */ public var apiKey: String? + /** + * (optional) custom headers to be sent for every HTTP request. + * default: empty + */ + public var customHeaders: [String:String]? = [:] /** * (optional) interval time we publish statistics collection data to the proxy. * The parameter is used only if the cache is enabled, otherwise the collection of the data is done directly @@ -42,12 +47,14 @@ public struct GoFeatureFlagProviderOptions { endpoint: String, pollInterval: TimeInterval = 60, apiKey: String? = nil, + customHeaders: [String:String]? = [:], dataFlushInterval: TimeInterval = 600, exporterMetadata: [String:ExporterMetadataValue]? = [:], networkService: NetworkingService? = URLSession.shared) { self.endpoint = endpoint self.pollInterval = pollInterval self.apiKey = apiKey + self.customHeaders = customHeaders self.networkService = networkService self.dataCollectorInterval = dataFlushInterval self.exporterMetadata = exporterMetadata diff --git a/Sources/GOFeatureFlag/provider.swift b/Sources/GOFeatureFlag/provider.swift index 55fcccc..5e86777 100644 --- a/Sources/GOFeatureFlag/provider.swift +++ b/Sources/GOFeatureFlag/provider.swift @@ -20,7 +20,7 @@ public final class GoFeatureFlagProvider: FeatureProvider { networkService = netSer } - var headers: [String:String] = [:] + var headers: [String:String] = options.customHeaders ?? [:] if let apiKey = options.apiKey { headers["Authorization"] = "Bearer \(apiKey)" } diff --git a/Tests/GOFeatureFlagTests/goff_api_tests.swift b/Tests/GOFeatureFlagTests/goff_api_tests.swift index 0d7f319..fc1dc6f 100644 --- a/Tests/GOFeatureFlagTests/goff_api_tests.swift +++ b/Tests/GOFeatureFlagTests/goff_api_tests.swift @@ -203,4 +203,29 @@ class GoffApiTests: XCTestCase { XCTFail("Expected GoFeatureFlagError.noEventToSend but got a different error: \(error).") } } + + func testShouldSendCustomHeaders() async throws{ + let mockService = MockNetworkingService(mockStatus: 200) + let options = GeFeatureFlagProviderOptions( + endpoint: "http://localhost:1031/", + customHeaders: ["X-Custom-Headers": "custom-value"] + ) + let goffAPI = GoFeatureFlagAPI(networkingService: mockService, options: options) + + let events: [FeatureEvent] = [ + FeatureEvent(kind: "feature", userKey: "981f2662-1fb4-4732-ac6d-8399d9205aa9", creationDate: Int64(Date().timeIntervalSince1970), + key: "flag-1", variation: "enabled", value: JSONValue.bool(true), default: false, version: nil, source: "PROVIDER_CACHE") + ] + + do { + _ = try await goffAPI.postDataCollector(events: events) + guard let request = mockService.requests.last else { + XCTFail("No request captured") + return + } + XCTAssertEqual("custom-value", request.allHTTPHeaderFields?["X-Custom-Header"]) + } catch { + XCTFail("exception thrown when doing the evaluation: \(error)") + } + } } diff --git a/Tests/GOFeatureFlagTests/provider_tests.swift b/Tests/GOFeatureFlagTests/provider_tests.swift index 75d53d2..f725685 100644 --- a/Tests/GOFeatureFlagTests/provider_tests.swift +++ b/Tests/GOFeatureFlagTests/provider_tests.swift @@ -204,4 +204,33 @@ class GoFeatureFlagProviderTests: XCTestCase { XCTAssertEqual(2, mockNetworkService.dataCollectorCallCounter) XCTAssertEqual(6, mockNetworkService.dataCollectorEventCounter) } + + func testProviderCustomHeaders() async { + let mockNetworkService = MockNetworkingService(mockStatus: 200) + let provider = GoFeatureFlagProvider( + options: GoFeatureFlagProviderOptions( + endpoint: "https://localhost:1031", + apiKey: "apiKey1" + customHeaders: [ + "X-Custom-Header": "custom-value", + "Authorization": "Bearer custom" // should be overwritten by apiKey + ] + networkService: mockNetworkService + ) + ) + let evaluationCtx = MutableContext(targetingKey: "ede04e44-463d-40d1-8fc0-b1d6855578d0") + let api = OpenFeatureAPI() + await api.setProviderAndWait(provider: provider, initialContext: evaluationCtx) + let client = api.getClient() + + _ = client.getBooleanDetails(key: "my-flag", defaultValue: false) + + guard let request = mockNetworkService.requests.last else { + XCTFail("No request captured") + return + } + + XCTAssertEqual("custom-value", request.allHTTPHeaderFields?["X-Custom-Header"]) + XCTAssertEqual("Bearer apiKey1", request.allHTTPHeaderFields?["Authorization"]) + } }