- 2.1 - The
@FlagCollection
annotation - 2.2 - Nested Structures
- 2.3 - Configure
FlagCollection
's contribution to properties keypath generation
RealFlags allows you to describe complex nested structure for flags. You can, for example, create a collection inside another collection and create a data tree for feature flags which better describe your application's requirements.
In order to define a sub-collection inside a root structure (a FlagCollectionProtocol
conform object) you must use the @FlagProtocol
: it allows you to identify another FlagCollectionProtocol
collection.
Consider the following example:
struct AppFeatureFlags: FlagCollectionProtocol {
// ...
@FlagCollection(description: "Checkout Flags")
var checkout: CheckoutFlags
// ...
}
The checkout
variable referes to another sub collection named CheckoutFlags
:
struct CheckoutFlags: FlagCollectionProtocol {
// ...
}
Once loaded you can use the dot notation to explore data in type-safe manner:
if appFlags.checkout.someProperty == "..." { ... }
A structure may contains subcollections and properties as you wish.
While RealFlags leave you free to organize your feature flags the library's architecture itself encourage you to classify feature flags and group them according to your criteria.
You can stay flat or you can create nested categories.
A single struct conforms to FlagCollectionProtocol
may contains both properties and other collections.
Consider the following example:
We have created a nested structure where the root is AppFeatureFlags
.
We can translate the following tree in RealFlags as follow (not all properties are exposed for brevity):
struct AppFeatureFlags: FlagCollectionProtocol {
@FlagCollection(description: "User's Related Flags")
var user: UserFlags
@FlagCollection(description: "Experimental Flags")
var experimental: ExperimentalFlags
@FlagCollection(description: "Checkout Flags")
var checkout: CheckoutFlags
@FlagCollection(description: "Color Themen")
var colorTheme: JSONData?
}
struct UserFlags: FlagCollectionProtocol {
@Flag(default: false, description: "Show Social Login Button")
var showSocialLogin: Bool
// ...
}
struct ExperimentalFlags: FlagCollectionProtocol {
@Flag(default: false, description: "New cool cache algorithm")
var enableFastPrecache: Bool
@Flag(key: "list_layout", default: nil, description: "Layout settings (JSON)")
var listLayoutSettings: JSONData?
}
struct CheckoutFlags: FlagCollectionProtocol {
// ...
}
Now you have created the structure you can easily load the structure in a FlagsLoader
instance and query their values with typesafe:
let appFlags = FlagsLoader(AppFeatureFlags.self, providers: [localProvider])
/// Somewhere in your code you can query a nested value
if appFlags.userFlags.showSocialLogin {
// ...
}
Sometimes you may want to organize a collection of flags by creating nested structures which are transparent to the keypath generation to its inner properties.
Consider the following example with a FlagLoader
with the default key configuration:
private struct TestFlags: FlagCollectionProtocol {
@FlagCollection(description: "Group 1")
var firstGroup: FirstGroup
@Flag(default: false, description: "Top level test flag")
var topLevelFlag: Bool
}
private struct FirstGroup: FlagCollectionProtocol {
@Flag(default: false, description: "Second level test flag")
var secondLevelFlag: Bool
}
You may want both topLevelFlag
and secondLevelFlag
contains the same keypath components.
By now you will get the following keypaths:
topLevelFlag
:top-level-flag
secondLevelFlag
:first-group/second-level-flag
While you want to ignore first-group
path component.
In order to accomplish this requirement you need to specify a keyConfiguration
to the FirstGroup
:
@FlagCollection(keyConfiguration: .skip, description: "Group 1")
var firstGroup: FirstGroup
.skip
allows you to ignore the firstGroup
property to the contribution of keypath.
Allowed transformations are pretty similar to the keyConfiguration
of the @Flag
property wrapper. They are:
default
: this is the default behaviour, it just uses the parent'sFlagLoader
'skeyConfiguration
setting.kebabCase
: refers to the style of writing in which each space is replaced by a-
character. It uses the kebab case with the current property key (first-group/...
)snakeCase
: refers to the style of writing in which each space is replaced by a_
character. It uses the snake case with the current property key (first_group/...
)skip
: ignore the current's collection contribution to keypath generation.custom(String)
: uses a fixed value to describe the current keypath component.