1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::borrow::Cow;
use chrono::{DateTime, Utc};
use crate::{
bandits::{BanditConfiguration, BanditResponse},
ufc::UniversalFlagConfig,
Str,
};
/// Hashing algorithm to use for bandit evaluation.
#[derive(Debug, Clone, Copy)]
pub(crate) enum BanditHashingAlgorithm {
Md5,
CityHash,
}
/// Remote configuration for the eppo client. It's a central piece that defines client behavior.
#[derive(Debug)]
pub struct Configuration {
/// Timestamp when configuration was fetched by the SDK.
pub(crate) fetched_at: DateTime<Utc>,
/// Flags configuration.
pub(crate) flags: UniversalFlagConfig,
/// Bandits configuration.
pub(crate) bandits: Option<BanditResponse>,
/// Hashing algorithm for bandit evaluation.
pub(crate) bandit_hashing_algorithm: BanditHashingAlgorithm,
}
impl Configuration {
/// Create a new configuration from server responses.
pub fn from_server_response(
config: UniversalFlagConfig,
bandits: Option<BanditResponse>,
) -> Configuration {
let now = Utc::now();
// Check environment variable for experimental CityHash support
let bandit_hashing_algorithm = std::env::var("EPPO_EXPERIMENTAL_BANDITS_CITYHASH")
.ok()
.and_then(|val| {
if val == "1" || val == "true" || val == "TRUE" {
Some(BanditHashingAlgorithm::CityHash)
} else {
None
}
})
.unwrap_or(BanditHashingAlgorithm::Md5);
Configuration {
fetched_at: now,
flags: config,
bandits,
bandit_hashing_algorithm,
}
}
/// Return a bandit variant for the specified flag key and string flag variation.
pub(crate) fn get_bandit_key<'a>(&'a self, flag_key: &str, variation: &str) -> Option<&'a Str> {
self.flags
.compiled
.flag_to_bandit_associations
.get(flag_key)
.and_then(|x| x.get(variation))
.map(|variation| &variation.key)
}
/// Return bandit configuration for the given key.
///
/// Returns `None` if bandits are missing for bandit does not exist.
pub(crate) fn get_bandit(&self, bandit_key: &str) -> Option<&BanditConfiguration> {
self.bandits.as_ref()?.bandits.get(bandit_key)
}
/// Returns an iterator over all flag keys. Note that this may return both disabled flags and
/// flags with bad configuration. Mostly useful for debugging.
pub fn flag_keys(&self) -> impl Iterator<Item = &Str> {
self.flags.compiled.flags.keys()
}
/// Returns an iterator over all bandit keys. Mostly useful to debugging.
pub fn bandit_keys(&self) -> impl Iterator<Item = &Str> {
self.bandits.iter().flat_map(|it| it.bandits.keys())
}
/// Returns bytes representing flags configuration.
///
/// The return value should be treated as opaque and passed on to another Eppo client for
/// initialization.
pub fn get_flags_configuration(&self) -> Option<Cow<[u8]>> {
Some(Cow::Borrowed(self.flags.to_json()))
}
/// Returns bytes representing bandits configuration.
///
/// The return value should be treated as opaque and passed on to another Eppo client for
/// initialization.
pub fn get_bandits_configuration(&self) -> Option<Cow<[u8]>> {
let bandits = self.bandits.as_ref()?;
serde_json::to_vec(bandits)
.inspect_err(|err| {
log::warn!(target: "eppo", "failed to serialize bandits: {err:?}");
})
.ok()
.map(Cow::Owned)
}
}