Skip to content
Open
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
36 changes: 36 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ let package = Package(
targets: ["ABSmartly"])
],
dependencies: [
.package(url: "https://github.com/stephencelis/SQLite.swift.git", .upToNextMajor(from: "0.14.1")),
.package(url: "https://github.com/Kitura/CircuitBreaker.git", .upToNextMajor(from: "5.0.3")),
.package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.0.2")),
.package(url: "https://github.com/mxcl/PromiseKit.git", .upToNextMajor(from: "6.8.4")),
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", .upToNextMajor(from: "4.3.0"))
],
targets: [
.target(
name: "ABSmartly",
dependencies: [.product(name: "Atomics", package: "swift-atomics"), "PromiseKit", "SwiftyJSON"],
dependencies: [.product(name: "Atomics", package: "swift-atomics"), "PromiseKit", "SwiftyJSON", "CircuitBreaker", .product(name: "SQLite", package: "SQLite.swift")],
path: "Sources/ABSmartly"),
.testTarget(
name: "ABSmartlyTests",
Expand Down
14 changes: 12 additions & 2 deletions Sources/ABSmartly/ABSmartlyConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,36 @@ public class ABSmartlyConfig {
var contextEventLogger: ContextEventLogger?
var variableParser: VariableParser?
var client: Client?
var resilienceConfig: ResilienceConfig?

public init() {
}

public convenience init(client: Client) {
print("Hello, world!")
self.init(
contextDataProvider: nil, contextEventHandler: nil, contextEventLogger: nil, variableParser: nil,
scheduler: nil, client: client)
scheduler: nil, client: client, resilienceConfig: nil)
}

public convenience init(client: Client, resilienceConfig: ResilienceConfig) {
print("Hello, world!")
self.init(
contextDataProvider: nil, contextEventHandler: nil, contextEventLogger: nil, variableParser: nil,
scheduler: nil, client: client, resilienceConfig: resilienceConfig)
}

public init(
contextDataProvider: ContextDataProvider?, contextEventHandler: ContextEventHandler?,
contextEventLogger: ContextEventLogger?,
variableParser: VariableParser?, scheduler: Scheduler?, client: Client?
variableParser: VariableParser?, scheduler: Scheduler?, client: Client?, resilienceConfig: ResilienceConfig?
) {
self.scheduler = scheduler
self.contextDataProvider = contextDataProvider
self.contextEventHandler = contextEventHandler
self.contextEventLogger = contextEventLogger
self.variableParser = variableParser
self.client = client
self.resilienceConfig = resilienceConfig
}
}
30 changes: 21 additions & 9 deletions Sources/ABSmartly/ABSmartlySDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@ public final class ABSmartlySDK {
scheduler = config.scheduler ?? DefaultScheduler()
client = config.client

if config.contextDataProvider == nil || config.contextEventHandler == nil {
if client == nil {
throw ABSmartlyError("Missing Client instance")
}

contextDataProvider = config.contextDataProvider ?? DefaultContextDataProvider(client: client!)
contextEventHandler = config.contextEventHandler ?? DefaultContextEventHandler(client: client!)
if config.resilienceConfig != nil {
contextEventHandler = ResilientContextEventHandler(
client: client!,
resilienceConfig: config.resilienceConfig!
)
contextDataProvider = ResilientContextDataProvider(
client: client!,
localCache: config.resilienceConfig!.localCache
)
} else {
contextDataProvider = config.contextDataProvider!
contextEventHandler = config.contextEventHandler!
if config.contextDataProvider == nil || config.contextEventHandler == nil {
if client == nil {
throw ABSmartlyError("Missing Client instance")
}

contextDataProvider = config.contextDataProvider ?? DefaultContextDataProvider(client: client!)
contextEventHandler = config.contextEventHandler ?? DefaultContextEventHandler(client: client!)
} else {
contextDataProvider = config.contextDataProvider!
contextEventHandler = config.contextEventHandler!
}
}

}

public func createContextWithData(config: ContextConfig, contextData: ContextData) -> Context {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ABSmartly/Attribute.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct Attribute: Encodable, Equatable {
public struct Attribute: Encodable, Equatable, Decodable {
public let name: String

public let value: JSON
Expand Down
116 changes: 116 additions & 0 deletions Sources/ABSmartly/CircuitBreakerHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// Created by Hermes Waldemarin on 14/03/2023.
//

import CircuitBreaker
import Foundation
import PromiseKit

public class CircuitBreakerHelper {

private var circuitBreaker: CircuitBreaker<Promise<Void>, Resolver<BreakerError?>>!
private let scheduler: Scheduler = DefaultScheduler()
private let timeoutLock = NSLock()
private let flushLock = NSLock()
private var flushInExecution = false
private var timeout: ScheduledHandle?
private var backoffPeriodInMilliseconds: Int?
private var handler: ContextEventHandler?

public init(resilienceConfig: ResilienceConfig, handler: ContextEventHandler) {
self.backoffPeriodInMilliseconds = resilienceConfig.backoffPeriodInMilliseconds
self.circuitBreaker = CircuitBreaker(
name: "Circuit1",
timeout: resilienceConfig.timeoutInMilliseconds,
maxFailures: resilienceConfig.failureRateThreshold,
command: callFunction,
fallback: fallback)
self.handler = handler
}

public func decorate(promise: Promise<Void>, fallBackResolver: Resolver<BreakerError?>) -> Promise<Void> {
return Promise<Void> { seal in
circuitBreaker.run(commandArgs: promise, fallbackArgs: fallBackResolver)
seal.fulfill(())
}
}

func callFunction(invocation: Invocation<Promise<Void>, Resolver<BreakerError?>>) {
let promise = invocation.commandArgs
let fallBackResolver = invocation.fallbackArgs

promise.done { response in
fallBackResolver.fulfill(nil)
var state = self.circuitBreaker.breakerState
invocation.notifySuccess()
if (state == .halfopen) && !self.flushInExecution {
self.flushInExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.timeoutLock.lock()
defer { self.timeoutLock.unlock() }
self.handler?.flushCache()
self.flushInExecution = false
}
}

}.catch { error in
var key = ""
var message = ""
if error is LocalizedError {
let localizedError = error as! LocalizedError
key = localizedError._domain
message = localizedError.errorDescription ?? ""
} else {
let nsError = error as! NSError
key = error._domain
nsError.code
message = error.localizedDescription
}

invocation.notifyFailure(
error: BreakerError(
key: key,
reason: message
)
)
}
}

private func fallback(err: BreakerError, fallBackPromise: Resolver<BreakerError?>) {
fallBackPromise.fulfill(err)
if err == .fastFail {
setTimeout()
} else {
}
}

private func clearTimeout() {
if timeout != nil {
timeoutLock.lock()
defer { timeoutLock.unlock() }

timeout?.cancel()
timeout = nil
}
}

private func setTimeout() {
if timeout == nil {
timeoutLock.lock()
defer { timeoutLock.unlock() }

if timeout == nil {
timeout = scheduler.schedule(
after: Double((backoffPeriodInMilliseconds! / 1000)),
execute: { [self] in
clearTimeout()
if circuitBreaker.breakerState != State.closed {
print("Resilience entering in half open state")
circuitBreaker.forceHalfOpen()
}

})
}
}
}
}
5 changes: 3 additions & 2 deletions Sources/ABSmartly/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public final class Context {
seal.fulfill(self)
} else if let ready = readyPromise {
_ = ready.done {
self.handler.flushCache()
seal.fulfill(self)
}
}
Expand Down Expand Up @@ -263,15 +264,15 @@ public final class Context {
}

public func getAttributes() -> [String: JSON] {
var result: [String:JSON] = [:]
var result: [String: JSON] = [:]

contextLock.lock()
defer { contextLock.unlock() }

for attribute in attributes {
result[attribute.name] = attribute.value
}
return result;
return result
}

public func setAttributes(_ attributes: [String: JSON]) {
Expand Down
4 changes: 4 additions & 0 deletions Sources/ABSmartly/DefaultContextEventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public class DefaultContextEventHandler: ContextEventHandler {
public func publish(event: PublishEvent) -> Promise<Void> {
return client.publish(event: event)
}

public func flushCache() {

}
}
2 changes: 1 addition & 1 deletion Sources/ABSmartly/Exposure.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct Exposure: Encodable, Equatable {
public struct Exposure: Encodable, Equatable, Decodable {
public let id: Int
public let name: String
public let unit: String?
Expand Down
2 changes: 1 addition & 1 deletion Sources/ABSmartly/GoalAchievement.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct GoalAchievement: Encodable, Equatable {
public struct GoalAchievement: Encodable, Equatable, Decodable {
public let name: String
public let achievedAt: Int64
public let properties: [String: JSON]?
Expand Down
25 changes: 25 additions & 0 deletions Sources/ABSmartly/MemoryCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Created by Hermes Waldemarin on 09/03/2023.
//

import Foundation
import PromiseKit
import SQLite

public class MemoryCache: SqlliteCache {
public override init() {
}

public override func getConnection() -> Connection {
do {
if db == nil {
db = try! Connection(.inMemory)
setupDatabase()
}
} catch {
print(error)
}
return self.db!
}

}
1 change: 1 addition & 0 deletions Sources/ABSmartly/Protocols/ContextEventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import PromiseKit
// sourcery: AutoMockable
public protocol ContextEventHandler {
func publish(event: PublishEvent) -> Promise<Void>
func flushCache()
}
13 changes: 13 additions & 0 deletions Sources/ABSmartly/Protocols/LocalCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Created by Hermes Waldemarin on 09/03/2023.
//

import Foundation
import PromiseKit

public protocol LocalCache {
func writePublishEvent(event: PublishEvent)
func retrievePublishEvents() -> [PublishEvent]
func writeContextData(contextData: ContextData)
func getContextData() -> ContextData?
}
Loading