Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [WIP] Add RemoteClient to Confidence #90

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extension ConfidenceDemoApp {
}

let confidence = Confidence.Builder(clientSecret: secret)
.withRegion(region: .europe)
.withInitializationstrategy(initializationStrategy: initializationStrategy)
.build()
let provider = ConfidenceFeatureProvider(confidence: confidence)
Expand All @@ -39,8 +40,8 @@ extension ConfidenceDemoApp {
Task {
await OpenFeatureAPI.shared.setProviderAndWait(provider: provider, initialContext: ctx)
confidence.send(
definition: "my_event",
payload: ["my_string_field": ConfidenceValue(string: "hello_from_world")])
definition: "abcd",
payload: ["my_key": ConfidenceValue(string: "hello_from_world")])
}
}
}
15 changes: 14 additions & 1 deletion Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,32 @@
public var region: ConfidenceRegion
public var initializationStrategy: InitializationStrategy
private var removedContextKeys: Set<String> = Set()
private var client: ConfidenceClient

required public init(
clientSecret: String,
timeout: TimeInterval,
region: ConfidenceRegion,
initializationStrategy: InitializationStrategy,
context: ConfidenceStruct = [:],
client: ConfidenceClient,
parent: ConfidenceEventSender? = nil
) {
self.clientSecret = clientSecret
self.timeout = timeout
self.region = region
self.initializationStrategy = initializationStrategy
self.context = context
self.client = client
self.parent = parent
}

// TODO: Implement actual event uploading to the backend
public func send(definition: String, payload: ConfidenceStruct) {
print("Sending: \"\(definition)\".\nMessage: \(payload)\nContext: \(context)")
Task {
try? await client.send(definition: definition, payload: payload)
}
}


Expand Down Expand Up @@ -58,6 +64,7 @@
region: region,
initializationStrategy: initializationStrategy,
context: context,
client: client,
parent: self)
}
}
Expand Down Expand Up @@ -94,7 +101,13 @@
clientSecret: clientSecret,
timeout: timeout,
region: region,
initializationStrategy: initializationStrategy
initializationStrategy: initializationStrategy,
client: RemoteConfidenceClient(
options: ConfidenceClientOptions(credentials: ConfidenceClientCredentials.clientSecret(secret: clientSecret), region: region),

Check warning on line 106 in Sources/Confidence/Confidence.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 146 characters (line_length)
metadata: ConfidenceMetadata(
name: "SDK_ID_SWIFT_CONFIDENCE",
version: "0.1.4") // x-release-please-version
)
)
}
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/Confidence/ConfidenceClient/ConfidenceClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public protocol ConfidenceClient {
func send(definition: String, payload: ConfidenceStruct) async throws
}

Check warning on line 6 in Sources/Confidence/ConfidenceClient/ConfidenceClient.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

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

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

public enum ConfidenceClientCredentials {
case clientSecret(secret: String)

public func getSecret() -> String {
switch self {
case .clientSecret(let secret):
return secret
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Foundation

public class RemoteConfidenceClient: ConfidenceClient {
private var options: ConfidenceClientOptions
private let metadata: ConfidenceMetadata
private var httpClient: HttpClient

init(
options: ConfidenceClientOptions,
session: URLSession? = nil,
metadata: ConfidenceMetadata
) {
self.options = options
self.httpClient = NetworkClient(session: session, region: options.region)
self.metadata = metadata
}

public func send(definition: String, payload: ConfidenceStruct) async throws {
let request = PublishEventRequest(
eventDefinition: definition,
payload: payload,
clientSecret: options.credentials.getSecret(),
sdk: Sdk(id: metadata.name, version: metadata.version)
)

do {
let result: HttpClientResult<PublishEventResponse> =
try await self.httpClient.post(path: ":publish", data: request)
switch result {
case .success(let successData):
guard successData.response.status == .ok else {
throw successData.response.mapStatusToError(error: successData.decodedError)
}
return
case .failure(let errorData):
throw handleError(error: errorData)
}
}
}

private func handleError(error: Error) -> Error {
if error is ConfidenceError {
return error
} else {
return ConfidenceError.grpcError(message: "\(error)")
}
}
}

struct PublishEventRequest: Encodable {
var eventDefinition: String
var payload: ConfidenceStruct
var clientSecret: String
var sdk: Sdk
}

struct PublishEventResponse: Codable {
}

struct Sdk: Encodable {
init(id: String?, version: String?) {
self.id = id ?? "SDK_ID_SWIFT_PROVIDER"
self.version = version ?? "unknown"
}

var id: String
var version: String
}
6 changes: 6 additions & 0 deletions Sources/Confidence/ConfidenceMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public struct ConfidenceMetadata {
public var name: String? = "SDK_ID_SWIFT_PROVIDER"
public var version: String?
}
40 changes: 40 additions & 0 deletions Sources/Confidence/Http/HttpClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

typealias HttpClientResult<T> = Result<HttpClientResponse<T>, Error>

protocol HttpClient {
func post<T: Decodable>(path: String, data: Encodable) async throws -> HttpClientResult<T>
}

struct HttpClientResponse<T> {
var decodedData: T?
var decodedError: HttpError?
var response: HTTPURLResponse
}

struct HttpError: Codable {
var code: Int
var message: String
var details: [String]
}

enum HttpClientError: Error {
case invalidResponse
case internalError
}

extension HTTPURLResponse {
func mapStatusToError(error: HttpError?, flag: String = "unknown") -> Error {
let defaultError = ConfidenceError.internalError(
message: "General error: \(error?.message ?? "Unknown error")")

switch self.status {
case .notFound:
return ConfidenceError.badRequest(message: flag) // TODO
case .badRequest:
return ConfidenceError.badRequest(message: error?.message ?? "")
default:
return defaultError
}
}
}
Loading
Loading