Skip to content

Commit

Permalink
Reformat the files I couldn't merge and fix up some force-unwraps in …
Browse files Browse the repository at this point in the history
…the tests
  • Loading branch information
gazreese committed May 2, 2024
1 parent f0e9df2 commit 2e83ab3
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 385 deletions.
105 changes: 61 additions & 44 deletions FlagsmithClient/Classes/Flag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,66 @@
import Foundation

/**
A Flag represents a feature flag on the server.
*/
A Flag represents a feature flag on the server.
*/
public struct Flag: Codable, Sendable {
enum CodingKeys: String, CodingKey {
case feature
case value = "feature_state_value"
case enabled
}

public let feature: Feature
public let value: TypedValue
public let enabled: Bool

public init(featureName:String, boolValue: Bool, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.init(featureName: featureName, value: TypedValue.bool(boolValue), enabled: enabled, featureType: featureType, featureDescription: featureDescription)
}

public init(featureName:String, floatValue: Float, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.init(featureName: featureName, value: TypedValue.float(floatValue), enabled: enabled, featureType: featureType, featureDescription: featureDescription)
}

public init(featureName:String, intValue: Int, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.init(featureName: featureName, value: TypedValue.int(intValue), enabled: enabled, featureType: featureType, featureDescription: featureDescription)
}

public init(featureName:String, stringValue: String, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.init(featureName: featureName, value: TypedValue.string(stringValue), enabled: enabled, featureType: featureType, featureDescription: featureDescription)
}

public init(featureName:String, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.init(featureName: featureName, value: TypedValue.null, enabled: enabled, featureType: featureType, featureDescription: featureDescription)
}

public init(featureName:String, value: TypedValue, enabled: Bool, featureType:String? = nil, featureDescription:String? = nil) {
self.feature = Feature(name: featureName, type: featureType, description: featureDescription)
self.value = value
self.enabled = enabled
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.feature, forKey: .feature)
try container.encode(self.value, forKey: .value)
try container.encode(self.enabled, forKey: .enabled)
}
enum CodingKeys: String, CodingKey {
case feature
case value = "feature_state_value"
case enabled
}

public let feature: Feature
public let value: TypedValue
public let enabled: Bool

public init(featureName: String, boolValue: Bool, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
self.init(featureName: featureName, value: TypedValue.bool(boolValue), enabled: enabled,
featureType: featureType, featureDescription: featureDescription)
}

public init(featureName: String, floatValue: Float, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
self.init(featureName: featureName, value: TypedValue.float(floatValue), enabled: enabled,
featureType: featureType, featureDescription: featureDescription)
}

public init(featureName: String, intValue: Int, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
self.init(featureName: featureName, value: TypedValue.int(intValue), enabled: enabled,
featureType: featureType, featureDescription: featureDescription)
}

public init(featureName: String, stringValue: String, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
self.init(featureName: featureName, value: TypedValue.string(stringValue), enabled: enabled,
featureType: featureType, featureDescription: featureDescription)
}

public init(featureName: String, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
self.init(featureName: featureName, value: TypedValue.null, enabled: enabled,
featureType: featureType, featureDescription: featureDescription)
}

public init(featureName: String, value: TypedValue, enabled: Bool,
featureType: String? = nil, featureDescription: String? = nil)
{
feature = Feature(name: featureName, type: featureType, description: featureDescription)
self.value = value
self.enabled = enabled
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(feature, forKey: .feature)
try container.encode(value, forKey: .value)
try container.encode(enabled, forKey: .enabled)
}
}
121 changes: 61 additions & 60 deletions FlagsmithClient/Classes/Flagsmith.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,48 @@

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
import FoundationNetworking
#endif

/// Manage feature flags and remote config across multiple projects,
/// environments and organisations.
public final class Flagsmith: @unchecked Sendable {
/// Shared singleton client object
public static let shared: Flagsmith = Flagsmith()
public static let shared: Flagsmith = .init()
private let apiManager: APIManager
private let analytics: FlagsmithAnalytics

/// Base URL
///
/// The default implementation uses: `https://edge.api.flagsmith.com/api/v1`.
public var baseURL: URL {
set { apiManager.baseURL = newValue }
get { apiManager.baseURL }
set { apiManager.baseURL = newValue }
}

/// API Key unique to your organization.
///
/// This value must be provided before any request can succeed.
public var apiKey: String? {
set { apiManager.apiKey = newValue }
get { apiManager.apiKey }
set { apiManager.apiKey = newValue }
}

/// Is flag analytics enabled?
public var enableAnalytics: Bool {
set { analytics.enableAnalytics = newValue }
get { analytics.enableAnalytics }
set { analytics.enableAnalytics = newValue }
}

/// How often to send the flag analytics, in seconds
public var analyticsFlushPeriod: Int {
set { analytics.flushPeriod = newValue }
get { analytics.flushPeriod }
set { analytics.flushPeriod = newValue }
}

/// Default flags to fall back on if an API call fails
private var _defaultFlags: [Flag] = []
public var defaultFlags: [Flag] {
public var defaultFlags: [Flag] {
get {
apiManager.propertiesSerialAccessQueue.sync { _defaultFlags }
}
Expand All @@ -60,8 +60,8 @@ public final class Flagsmith: @unchecked Sendable {
}

/// Configuration class for the cache settings
private var _cacheConfig: CacheConfig = CacheConfig()
public var cacheConfig: CacheConfig {
private var _cacheConfig: CacheConfig = .init()
public var cacheConfig: CacheConfig {
get {
apiManager.propertiesSerialAccessQueue.sync { _cacheConfig }
}
Expand All @@ -83,32 +83,30 @@ public final class Flagsmith: @unchecked Sendable {
/// - identity: ID of the user (optional)
/// - completion: Closure with Result which contains array of Flag objects in case of success or Error in case of failure
public func getFeatureFlags(forIdentity identity: String? = nil,
completion: @Sendable @escaping (Result<[Flag], any Error>) -> Void) {

completion: @Sendable @escaping (Result<[Flag], any Error>) -> Void)
{
if let identity = identity {
getIdentity(identity) { (result) in
getIdentity(identity) { result in
switch result {
case .success(let thisIdentity):
case let .success(thisIdentity):
completion(.success(thisIdentity.flags))
case .failure(let error):
case let .failure(error):
if self.defaultFlags.isEmpty {
completion(.failure(error))
}
else {
} else {
completion(.success(self.defaultFlags))
}
}
}
} else {
apiManager.request(.getFlags) { (result: Result<[Flag], Error>) in
switch result {
case .success(let flags):
case let .success(flags):
completion(.success(flags))
case .failure(let error):
case let .failure(error):
if self.defaultFlags.isEmpty {
completion(.failure(error))
}
else {
} else {
completion(.success(self.defaultFlags))
}
}
Expand All @@ -124,18 +122,18 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result which contains Bool in case of success or Error in case of failure
public func hasFeatureFlag(withID id: String,
forIdentity identity: String? = nil,
completion: @Sendable @escaping (Result<Bool, any Error>) -> Void) {
completion: @Sendable @escaping (Result<Bool, any Error>) -> Void)
{
analytics.trackEvent(flagName: id)
getFeatureFlags(forIdentity: identity) { (result) in
getFeatureFlags(forIdentity: identity) { result in
switch result {
case .success(let flags):
let hasFlag = flags.contains(where: {$0.feature.name == id && $0.enabled})
case let .success(flags):
let hasFlag = flags.contains(where: { $0.feature.name == id && $0.enabled })
completion(.success(hasFlag))
case .failure(let error):
if self.defaultFlags.contains(where: {$0.feature.name == id && $0.enabled}) {
case let .failure(error):
if self.defaultFlags.contains(where: { $0.feature.name == id && $0.enabled }) {
completion(.success(true))
}
else {
} else {
completion(.failure(error))
}
}
Expand All @@ -151,18 +149,18 @@ public final class Flagsmith: @unchecked Sendable {
@available(*, deprecated, renamed: "getValueForFeature(withID:forIdentity:completion:)")
public func getFeatureValue(withID id: String,
forIdentity identity: String? = nil,
completion: @Sendable @escaping (Result<String?, any Error>) -> Void) {
completion: @Sendable @escaping (Result<String?, any Error>) -> Void)
{
analytics.trackEvent(flagName: id)
getFeatureFlags(forIdentity: identity) { (result) in
getFeatureFlags(forIdentity: identity) { result in
switch result {
case .success(let flags):
let flag = flags.first(where: {$0.feature.name == id})
case let .success(flags):
let flag = flags.first(where: { $0.feature.name == id })
completion(.success(flag?.value.stringValue))
case .failure(let error):
case let .failure(error):
if let flag = self.getFlagUsingDefaults(withID: id, forIdentity: identity) {
completion(.success(flag.value.stringValue))
}
else {
} else {
completion(.failure(error))
}
}
Expand All @@ -177,18 +175,18 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result of `TypedValue` in case of success or `Error` in case of failure
public func getValueForFeature(withID id: String,
forIdentity identity: String? = nil,
completion: @Sendable @escaping (Result<TypedValue?, any Error>) -> Void) {
completion: @Sendable @escaping (Result<TypedValue?, any Error>) -> Void)
{
analytics.trackEvent(flagName: id)
getFeatureFlags(forIdentity: identity) { (result) in
getFeatureFlags(forIdentity: identity) { result in
switch result {
case .success(let flags):
let flag = flags.first(where: {$0.feature.name == id})
case let .success(flags):
let flag = flags.first(where: { $0.feature.name == id })
completion(.success(flag?.value))
case .failure(let error):
case let .failure(error):
if let flag = self.getFlagUsingDefaults(withID: id, forIdentity: identity) {
completion(.success(flag.value))
}
else {
} else {
completion(.failure(error))
}
}
Expand All @@ -203,17 +201,18 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result which contains array of Trait objects in case of success or Error in case of failure
public func getTraits(withIDS ids: [String]? = nil,
forIdentity identity: String,
completion: @Sendable @escaping (Result<[Trait], any Error>) -> Void) {
getIdentity(identity) { (result) in
completion: @Sendable @escaping (Result<[Trait], any Error>) -> Void)
{
getIdentity(identity) { result in
switch result {
case .success(let identity):
case let .success(identity):
if let ids = ids {
let traits = identity.traits.filter({ids.contains($0.key)})
let traits = identity.traits.filter { ids.contains($0.key) }
completion(.success(traits))
} else {
completion(.success(identity.traits))
}
case .failure(let error):
case let .failure(error):
completion(.failure(error))
}
}
Expand All @@ -227,13 +226,14 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result which contains Trait in case of success or Error in case of failure
public func getTrait(withID id: String,
forIdentity identity: String,
completion: @Sendable @escaping (Result<Trait?, any Error>) -> Void) {
getIdentity(identity) { (result) in
completion: @Sendable @escaping (Result<Trait?, any Error>) -> Void)
{
getIdentity(identity) { result in
switch result {
case .success(let identity):
let trait = identity.traits.first(where: {$0.key == id})
case let .success(identity):
let trait = identity.traits.first(where: { $0.key == id })
completion(.success(trait))
case .failure(let error):
case let .failure(error):
completion(.failure(error))
}
}
Expand All @@ -247,7 +247,8 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result which contains Trait in case of success or Error in case of failure
public func setTrait(_ trait: Trait,
forIdentity identity: String,
completion: @Sendable @escaping (Result<Trait, any Error>) -> Void) {
completion: @Sendable @escaping (Result<Trait, any Error>) -> Void)
{
apiManager.request(.postTrait(trait: trait, identity: identity)) { (result: Result<Trait, Error>) in
completion(result)
}
Expand All @@ -261,7 +262,8 @@ public final class Flagsmith: @unchecked Sendable {
/// - completion: Closure with Result which contains Traits in case of success or Error in case of failure
public func setTraits(_ traits: [Trait],
forIdentity identity: String,
completion: @Sendable @escaping (Result<[Trait], any Error>) -> Void) {
completion: @Sendable @escaping (Result<[Trait], any Error>) -> Void)
{
apiManager.request(.postTraits(identity: identity, traits: traits)) { (result: Result<Traits, Error>) in
completion(result.map(\.traits))
}
Expand All @@ -273,22 +275,22 @@ public final class Flagsmith: @unchecked Sendable {
/// - identity: ID of the user
/// - completion: Closure with Result which contains Identity in case of success or Error in case of failure
public func getIdentity(_ identity: String,
completion: @Sendable @escaping (Result<Identity, any Error>) -> Void) {
completion: @Sendable @escaping (Result<Identity, any Error>) -> Void)
{
apiManager.request(.getIdentity(identity: identity)) { (result: Result<Identity, Error>) in
completion(result)
}
}

/// Return a flag for a flag ID from the default flags.
private func getFlagUsingDefaults(withID id: String, forIdentity identity: String? = nil) -> Flag? {
return self.defaultFlags.first(where: {$0.feature.name == id})
private func getFlagUsingDefaults(withID id: String, forIdentity _: String? = nil) -> Flag? {
return defaultFlags.first(where: { $0.feature.name == id })
}
}

public final class CacheConfig {

/// Cache to use when enabled, defaults to the shared app cache
public var cache: URLCache = URLCache.shared
public var cache: URLCache = .shared

/// Use cached flags as a fallback?
public var useCache: Bool = false
Expand All @@ -298,5 +300,4 @@ public final class CacheConfig {

/// Skip API if there is a cache available
public var skipAPI: Bool = false

}
Loading

0 comments on commit 2e83ab3

Please sign in to comment.