Skip to content

Commit

Permalink
refactor: Add Confidence Library scaffolding (#83)
Browse files Browse the repository at this point in the history
* chore: Update ignore

* Remove xcscheme file from repo

* Fix test script used in CI

* feat: First Confidence Scaffolding

* Basic EvaluationContext wiring in events

* More Builder and constructor refactoring

* Update README

* Move enum to standalone files

* Remove temporary code
  • Loading branch information
fabriziodemaria authored Apr 2, 2024
1 parent 1bd9525 commit 2e49e23
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 188 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
/**/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
Expand All @@ -10,4 +10,4 @@ DerivedData/
.build
.mockingbird
project.json
.swiftpm
.swiftpm

This file was deleted.

17 changes: 11 additions & 6 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ConfidenceProvider
import Confidence
import OpenFeature
import SwiftUI

Expand All @@ -21,17 +22,21 @@ extension ConfidenceDemoApp {
}

// If we have no cache, then do a fetch first.
var initializationStratgey: InitializationStrategy = .activateAndFetchAsync
var initializationStrategy: InitializationStrategy = .activateAndFetchAsync
if ConfidenceFeatureProvider.isStorageEmpty() {
initializationStratgey = .fetchAndActivate
initializationStrategy = .fetchAndActivate
}

let provider = ConfidenceFeatureProvider
.Builder(credentials: .clientSecret(secret: secret))
.with(initializationStrategy: initializationStratgey)
let confidence = Confidence.Builder(clientSecret: secret)
.withInitializationstrategy(initializationStrategy: initializationStrategy)
.build()
let provider = ConfidenceFeatureProvider(confidence: confidence)

// NOTE: Using a random UUID for each app start is not advised and can result in getting stale values.
let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure())
OpenFeatureAPI.shared.setProvider(provider: provider, initialContext: ctx)
Task {
await OpenFeatureAPI.shared.setProviderAndWait(provider: provider, initialContext: ctx)
confidence.send(eventName: "my_event")
}
}
}
13 changes: 11 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@
import PackageDescription

let package = Package(
name: "ConfidenceProvider",
name: "Confidence",
platforms: [
.iOS(.v14),
.macOS(.v12)
],
products: [
.library(
name: "ConfidenceProvider",
targets: ["ConfidenceProvider"])
targets: ["ConfidenceProvider"]),
.library(
name: "Confidence",
targets: ["Confidence"])
],
dependencies: [
.package(url: "[email protected]:open-feature/swift-sdk.git", from: "0.1.0"),
],
targets: [
.target(
name: "Confidence",
dependencies: [],
plugins: []
),
.target(
name: "ConfidenceProvider",
dependencies: [
.product(name: "OpenFeature", package: "swift-sdk"),
"Confidence"
],
plugins: []
),
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ In the dependencies section of Package.swift add:
and in the target dependencies section add:
```swift
.product(name: "ConfidenceProvider", package: "confidence-openfeature-provider-swift"),
.product(name: "Confidence", package: "confidence-openfeature-provider-swift"),
```

## Usage

### Import Modules

Import the `ConfidenceProvider` and `OpenFeature` modules
Import the `ConfidenceProvider`, the `Confidence` and the `OpenFeature` modules

```swift
import ConfidenceProvider
import Confidence
import OpenFeature
```

Expand Down
82 changes: 82 additions & 0 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Foundation

public class Confidence: ConfidenceEventSender {
public var context: [String: String]
public let clientSecret: String
public var timeout: TimeInterval
public var region: ConfidenceRegion
public var initializationStrategy: InitializationStrategy

init(
clientSecret: String,
timeout: TimeInterval,
region: ConfidenceRegion,
initializationStrategy: InitializationStrategy
) {
self.context = [:]
self.clientSecret = clientSecret
self.timeout = timeout
self.region = region
self.initializationStrategy = initializationStrategy
}

// TODO: Implement actual event uploading to the backend
public func send(eventName: String) {
print("Sending \(eventName) - Targeting key: \(context["targeting_key"] ?? "UNKNOWN")")
}

public func updateContextEntry(key: String, value: String) {
context[key] = value
}

public func removeContextEntry(key: String) {
context.removeValue(forKey: key)
}

public func clearContext() {
context = [:]
}

// TODO: Implement creation of child instances
public func withContext(_ context: [String: String]) -> Self {
return self
}
}

extension Confidence {
public class Builder {
let clientSecret: String
var timeout: TimeInterval = 10.0
var region: ConfidenceRegion = .global
var initializationStrategy: InitializationStrategy = .fetchAndActivate

public init(clientSecret: String) {
self.clientSecret = clientSecret
}

public func withTimeout(timeout: TimeInterval) -> Builder {
self.timeout = timeout
return self
}


public func withRegion(region: ConfidenceRegion) -> Builder {
self.region = region
return self
}

public func withInitializationstrategy(initializationStrategy: InitializationStrategy) -> Builder {
self.initializationStrategy = initializationStrategy
return self
}

public func build() -> Confidence {
return Confidence(
clientSecret: clientSecret,
timeout: timeout,
region: region,
initializationStrategy: initializationStrategy
)
}
}
}
7 changes: 7 additions & 0 deletions Sources/Confidence/ConfidenceEventSender.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

/// Sends events to Confidence. Contextual data is appended to each event
// TODO: Add functions for sending events with payload
public protocol ConfidenceEventSender: Contextual {
func send(eventName: String)
}
9 changes: 9 additions & 0 deletions Sources/Confidence/ConfidenceRegion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

/// Sets the region for the network request to the Confidence backend.
/// This is applied for both sending events as well as fetching flag's data.
public enum ConfidenceRegion {
case global
case europe
case usa
}
15 changes: 15 additions & 0 deletions Sources/Confidence/Contextual.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

/// A Contextual implementer maintains context data and can create child instances
/// that can still access their parent's data
public protocol Contextual {
// TODO: Add complex type to the context Dictionary
var context: [String: String] { get set }

func updateContextEntry(key: String, value: String)
func removeContextEntry(key: String)
func clearContext()
/// Creates a child Contextual instance that still has access
/// to its parent context
func withContext(_ context: [String: String]) -> Self
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation

/// Flag resolve configuration related to how to refresh flags at startup
public enum InitializationStrategy {
case fetchAndActivate, activateAndFetchAsync
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import Confidence

public struct ConfidenceClientOptions {
public var credentials: ConfidenceClientCredentials
public var timeout: TimeInterval
public var region: ConfidenceRegion
public var initializationStrategy: InitializationStrategy

public init(
credentials: ConfidenceClientCredentials,
timeout: TimeInterval? = nil,
region: ConfidenceRegion? = nil,
initializationStrategy: InitializationStrategy = .fetchAndActivate
) {
self.credentials = credentials
self.timeout = timeout ?? 10.0
self.region = region ?? .global
self.initializationStrategy = initializationStrategy
}
}

public enum ConfidenceClientCredentials {
case clientSecret(secret: String)

public func getSecret() -> String {
switch self {
case .clientSecret(let secret):
return secret
}
}
}
Loading

0 comments on commit 2e49e23

Please sign in to comment.