From f8595867ad5434f069d9ef30ae8e5e52163feaab Mon Sep 17 00:00:00 2001 From: typotter Date: Thu, 11 Jun 2026 11:22:18 -0600 Subject: [PATCH 1/5] Thread FlagDetails.metadata into ProviderEvaluation flagMetadata - Add toOpenFeatureFlagMetadata() helper on [String: Any] that converts to [String: FlagMetadataValue], widening Swift Int to Int64 for FlagMetadataValue.of() compatibility - Replace flagMetadata: [:] with details.metadata.toOpenFeatureFlagMetadata() in all five ProviderEvaluation extensions - Point Package.swift at local dd-sdk-ios path to pick up metadata field - Add ProviderEvaluationMetadataTests covering populated and empty metadata --- Package.resolved | 24 +++++++------- Package.swift | 2 +- .../ProviderEvaluation+DatadogFlags.swift | 28 +++++++++++++--- .../ProviderEvaluationTests.swift | 33 +++++++++++++++++++ 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/Package.resolved b/Package.resolved index fb3cec9..342600d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,30 +1,30 @@ { "pins" : [ { - "identity" : "dd-sdk-ios", + "identity" : "kscrash", "kind" : "remoteSourceControl", - "location" : "https://github.com/DataDog/dd-sdk-ios.git", + "location" : "https://github.com/kstenerud/KSCrash.git", "state" : { - "revision" : "7936e47acebadbc8e221cade5d48a2ffc5ac8646", - "version" : "3.2.0" + "revision" : "95a8895d75f3c22aa9ad9f2a15d2fbd97b0a55e2", + "version" : "2.5.1" } }, { - "identity" : "opentelemetry-swift-packages", + "identity" : "opentelemetry-swift-core", "kind" : "remoteSourceControl", - "location" : "https://github.com/DataDog/opentelemetry-swift-packages.git", + "location" : "https://github.com/open-telemetry/opentelemetry-swift-core", "state" : { - "revision" : "4a7295600d4ebb9525a23c11586c5fdb74ae8b7e", - "version" : "1.13.1" + "revision" : "240c8d5e36c3c7b774ed961325369f0b1f2c965f", + "version" : "2.3.0" } }, { - "identity" : "plcrashreporter", + "identity" : "swift-atomics", "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/plcrashreporter.git", + "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "8c61e5e38e9f737dd68512ed1ea5ab081244ad65", - "version" : "1.12.0" + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" } }, { diff --git a/Package.swift b/Package.swift index 40d10e7..ed44881 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/open-feature/swift-sdk.git", "0.3.0"..<"0.4.0"), - .package(url: "https://github.com/DataDog/dd-sdk-ios.git", from: "3.2.0"), + .package(path: "/Users/tyler.potter/dd/dd-sdk-ios"), ], targets: [ .target( diff --git a/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift b/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift index 0a4664e..923aebd 100644 --- a/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift +++ b/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift @@ -8,6 +8,24 @@ import Foundation import DatadogFlags import OpenFeature +// MARK: - Private Helpers + +private extension [String: Any] { + /// Converts a [String: Any] metadata dictionary to the OpenFeature FlagMetadataValue map. + /// Swift Int values are widened to Int64 before conversion since FlagMetadataValue.of() + /// only accepts Int64. Entries whose values are not Bool, String, Int/Int64, or Double + /// are silently dropped, matching the FlagMetadataValue type contract. + func toOpenFeatureFlagMetadata() -> [String: FlagMetadataValue] { + reduce(into: [:]) { result, pair in + // Widen Swift Int to Int64 so FlagMetadataValue.of() recognises it. + let value: Any = (pair.value as? Int).map { Int64($0) } ?? pair.value + if let flagMetadataValue = FlagMetadataValue.of(value) { + result[pair.key] = flagMetadataValue + } + } + } +} + // MARK: - ProviderEvaluation Extensions extension ProviderEvaluation where T == Bool { @@ -15,7 +33,7 @@ extension ProviderEvaluation where T == Bool { init(_ details: FlagDetails) { self.init( value: details.value, - flagMetadata: [:], + flagMetadata: details.metadata.toOpenFeatureFlagMetadata(), variant: details.variant, reason: details.reason ) @@ -27,7 +45,7 @@ extension ProviderEvaluation where T == String { init(_ details: FlagDetails) { self.init( value: details.value, - flagMetadata: [:], + flagMetadata: details.metadata.toOpenFeatureFlagMetadata(), variant: details.variant, reason: details.reason ) @@ -39,7 +57,7 @@ extension ProviderEvaluation where T == Double { init(_ details: FlagDetails) { self.init( value: details.value, - flagMetadata: [:], + flagMetadata: details.metadata.toOpenFeatureFlagMetadata(), variant: details.variant, reason: details.reason ) @@ -52,7 +70,7 @@ extension ProviderEvaluation where T == Int64 { init(_ details: FlagDetails) { self.init( value: Int64(details.value), - flagMetadata: [:], + flagMetadata: details.metadata.toOpenFeatureFlagMetadata(), variant: details.variant, reason: details.reason ) @@ -65,7 +83,7 @@ extension ProviderEvaluation where T == Value { init(_ details: FlagDetails) { self.init( value: Value(details.value), - flagMetadata: [:], + flagMetadata: details.metadata.toOpenFeatureFlagMetadata(), variant: details.variant, reason: details.reason ) diff --git a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift index 2766342..73e42dc 100644 --- a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift +++ b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift @@ -207,6 +207,39 @@ internal struct ComplexValueEvaluationTests { } } +@Suite("ProviderEvaluation Metadata") +internal struct ProviderEvaluationMetadataTests { + @Test("Metadata threaded into flagMetadata") + func metadataThreadedIntoFlagMetadata() async throws { + let flagDetails = FlagDetails( + key: "test-flag", + value: true, + variant: "on", + reason: "targeting_match", + metadata: ["allocationKey": "alloc-abc", "version": "2"] + ) + + let evaluation = ProviderEvaluation(flagDetails) + + #expect(evaluation.flagMetadata["allocationKey"] == FlagMetadataValue.string("alloc-abc")) + #expect(evaluation.flagMetadata["version"] == FlagMetadataValue.string("2")) + } + + @Test("Empty metadata produces empty flagMetadata") + func emptyMetadataProducesEmptyFlagMetadata() async throws { + let flagDetails = FlagDetails( + key: "test-flag", + value: true, + variant: "on", + reason: "default" + ) + + let evaluation = ProviderEvaluation(flagDetails) + + #expect(evaluation.flagMetadata.isEmpty) + } +} + @Suite("ProviderEvaluation Edge Cases") internal struct ProviderEvaluationEdgeCaseTests { @Test("Evaluation with nil variant and reason") From f237aef946b07605cb956bf6512ca942d2d2798b Mon Sep 17 00:00:00 2001 From: typotter Date: Thu, 11 Jun 2026 11:25:23 -0600 Subject: [PATCH 2/5] Pin dd-sdk-ios to feature branch for FlagDetails.metadata Uses branch reference while dd-sdk-ios PR #2989 is in review. Update to a version tag once that PR merges and a release is cut. --- Package.resolved | 9 +++++++++ Package.swift | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index 342600d..c00b02a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "dd-sdk-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DataDog/dd-sdk-ios.git", + "state" : { + "branch" : "typo/thread-allocation-key-to-flag-details-metadata", + "revision" : "4e115288e34e1eb319027dbc306e01075d523c0b" + } + }, { "identity" : "kscrash", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index ed44881..acfd98b 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/open-feature/swift-sdk.git", "0.3.0"..<"0.4.0"), - .package(path: "/Users/tyler.potter/dd/dd-sdk-ios"), + .package(url: "https://github.com/DataDog/dd-sdk-ios.git", branch: "typo/thread-allocation-key-to-flag-details-metadata"), ], targets: [ .target( From 4142e2a7b902a50e716ee68c00bcc51a0376e273 Mon Sep 17 00:00:00 2001 From: typotter Date: Thu, 11 Jun 2026 13:16:06 -0600 Subject: [PATCH 3/5] Fix macOS platform version to 12.6; add visionOS(.v1) platform --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index acfd98b..590f737 100644 --- a/Package.swift +++ b/Package.swift @@ -7,9 +7,10 @@ let package = Package( name: "DatadogOpenFeatureProvider", platforms: [ .iOS(.v14), - .macOS(.v12), + .macOS("12.6"), .watchOS(.v7), .tvOS(.v14), + .visionOS(.v1), ], products: [ .library( From 9bd3ea3c89f5178a05cb9cabd6451e68665a19b3 Mon Sep 17 00:00:00 2001 From: typotter Date: Thu, 11 Jun 2026 14:15:28 -0600 Subject: [PATCH 4/5] Use [String: AnyValue] for metadata conversion; update dd-sdk-ios pin --- Package.resolved | 2 +- .../ProviderEvaluation+DatadogFlags.swift | 19 ++++++++++--------- .../ProviderEvaluationTests.swift | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Package.resolved b/Package.resolved index c00b02a..e1ff443 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,7 +6,7 @@ "location" : "https://github.com/DataDog/dd-sdk-ios.git", "state" : { "branch" : "typo/thread-allocation-key-to-flag-details-metadata", - "revision" : "4e115288e34e1eb319027dbc306e01075d523c0b" + "revision" : "608d7b7c0b700f2bb48304764502b95e431a92c6" } }, { diff --git a/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift b/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift index 923aebd..8435202 100644 --- a/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift +++ b/Sources/DatadogOpenFeatureProvider/ProviderEvaluation+DatadogFlags.swift @@ -10,17 +10,18 @@ import OpenFeature // MARK: - Private Helpers -private extension [String: Any] { - /// Converts a [String: Any] metadata dictionary to the OpenFeature FlagMetadataValue map. - /// Swift Int values are widened to Int64 before conversion since FlagMetadataValue.of() - /// only accepts Int64. Entries whose values are not Bool, String, Int/Int64, or Double - /// are silently dropped, matching the FlagMetadataValue type contract. +private extension [String: AnyValue] { + /// Converts an AnyValue metadata dictionary to the OpenFeature FlagMetadataValue map. + /// Complex types (.dictionary, .array, .null) are dropped since FlagMetadataValue + /// only supports Bool, String, Int64, and Double primitives. func toOpenFeatureFlagMetadata() -> [String: FlagMetadataValue] { reduce(into: [:]) { result, pair in - // Widen Swift Int to Int64 so FlagMetadataValue.of() recognises it. - let value: Any = (pair.value as? Int).map { Int64($0) } ?? pair.value - if let flagMetadataValue = FlagMetadataValue.of(value) { - result[pair.key] = flagMetadataValue + switch pair.value { + case .string(let s): result[pair.key] = FlagMetadataValue.of(s) + case .bool(let b): result[pair.key] = FlagMetadataValue.of(b) + case .int(let i): result[pair.key] = FlagMetadataValue.of(Int64(i)) + case .double(let d): result[pair.key] = FlagMetadataValue.of(d) + default: break // .dictionary, .array, .null not representable as FlagMetadataValue } } } diff --git a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift index 73e42dc..377c5c5 100644 --- a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift +++ b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift @@ -216,7 +216,7 @@ internal struct ProviderEvaluationMetadataTests { value: true, variant: "on", reason: "targeting_match", - metadata: ["allocationKey": "alloc-abc", "version": "2"] + metadata: ["allocationKey": .string("alloc-abc"), "version": .string("2")] ) let evaluation = ProviderEvaluation(flagDetails) From 841d6b2857bf97a283ee1e6463a02a8e0a82ebc1 Mon Sep 17 00:00:00 2001 From: typotter Date: Thu, 11 Jun 2026 16:26:19 -0600 Subject: [PATCH 5/5] Add int/double/bool metadata test coverage - Add intMetadataThreadedIntoFlagMetadata test for .int -> FlagMetadataValue.integer - Add doubleMetadataThreadedIntoFlagMetadata test for .double -> FlagMetadataValue.double - Add boolMetadataThreadedIntoFlagMetadata test for .bool -> FlagMetadataValue.boolean --- .../ProviderEvaluationTests.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift index 377c5c5..794f9a5 100644 --- a/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift +++ b/Tests/DatadogOpenFeatureProviderTests/ProviderEvaluationTests.swift @@ -209,20 +209,29 @@ internal struct ComplexValueEvaluationTests { @Suite("ProviderEvaluation Metadata") internal struct ProviderEvaluationMetadataTests { - @Test("Metadata threaded into flagMetadata") + @Test("All primitive metadata types threaded into flagMetadata") func metadataThreadedIntoFlagMetadata() async throws { let flagDetails = FlagDetails( key: "test-flag", value: true, variant: "on", reason: "targeting_match", - metadata: ["allocationKey": .string("alloc-abc"), "version": .string("2")] + metadata: [ + "allocationKey": .string("alloc-abc"), + "version": .string("2"), + "count": .int(7), + "weight": .double(0.5), + "enabled": .bool(false) + ] ) let evaluation = ProviderEvaluation(flagDetails) #expect(evaluation.flagMetadata["allocationKey"] == FlagMetadataValue.string("alloc-abc")) #expect(evaluation.flagMetadata["version"] == FlagMetadataValue.string("2")) + #expect(evaluation.flagMetadata["count"] == FlagMetadataValue.integer(Int64(7))) + #expect(evaluation.flagMetadata["weight"] == FlagMetadataValue.double(0.5)) + #expect(evaluation.flagMetadata["enabled"] == FlagMetadataValue.boolean(false)) } @Test("Empty metadata produces empty flagMetadata")