Skip to content

Commit

Permalink
V1 matrix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed May 10, 2024
1 parent 2ef8bb6 commit 7e233d0
Show file tree
Hide file tree
Showing 99 changed files with 6,268 additions and 84 deletions.
6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ keywords = ["configcat", "feature-flag", "feature-toggle"]
license = "MIT"
version = "0.1.0"
edition = "2021"
build = "build.rs"

[dependencies]
serde = { version = "1.0", features = ["derive", "rc"] }
Expand All @@ -28,9 +27,6 @@ sha2 = "0.10"
base16ct = { version = "0.2", features = ["alloc"] }
semver = "1.0"

[build-dependencies]
built = "0.7"

[dev-dependencies]
mockito = "1.2.0"
tokio = { version = "1.17.0", features = ["macros"] }
tokio = { version = "1.17.0", features = ["rt-multi-thread"] }
3 changes: 0 additions & 3 deletions build.rs

This file was deleted.

105 changes: 104 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,66 @@ impl Client {
}
}

/// Create a new [`OptionsBuilder`] used to build a [`Client`].
///
/// # Errors
///
/// This method fails if the given SDK key is empty or has an invalid format.
///
/// # Examples
///
/// ```no_run
/// use std::time::Duration;
/// use configcat::{DataGovernance, Client, PollingMode};
///
/// let client = Client::builder("SDK_KEY")
/// .polling_mode(PollingMode::AutoPoll(Duration::from_secs(60)))
/// .data_governance(DataGovernance::EU)
/// .build()
/// .unwrap();
/// ```
pub fn builder(sdk_key: &str) -> OptionsBuilder {
OptionsBuilder::new(sdk_key)
}

/// Create a new [`Client`] with the default [`Options`].
///
/// # Errors
///
/// This method fails if the given SDK key is empty or has an invalid format.
///
/// # Examples
///
/// ```no_run
/// use configcat::Client;
///
/// let client = Client::new("SDK_KEY").unwrap();
/// ```
pub fn new(sdk_key: &str) -> Result<Self, ClientError> {
OptionsBuilder::new(sdk_key).build()
}

/// Initiate a force refresh on the cached config JSON data.
///
/// # Errors
///
/// This method fails in the following cases:
/// - The SDK is in offline mode.
/// - The SDK has a [`crate::OverrideBehavior::LocalOnly`] override set.
/// - The HTTP request that supposed to download the new config JSON fails.
///
/// # Examples
///
/// ```no_run
/// use configcat::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new("SDK_KEY").unwrap();
///
/// _ = client.refresh().await
/// }
/// ```
pub async fn refresh(&self) -> Result<(), ClientError> {
if self.options.offline() {
let err = ClientError::new(
Expand All @@ -42,10 +94,43 @@ impl Client {
self.service.refresh().await
}

/// Evaluate a feature flag identified by the given `key`.
///
/// Returns `default` if the flag doesn't exist, or there was an error during the evaluation.
///
/// # Examples
///
/// ```no_run
/// use configcat::{Client, User};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new("SDK_KEY").unwrap();
///
/// let user = User::new("user-id");
/// let value = client.get_bool_value("flag-key", Some(user), false).await;
/// }
/// ```
pub async fn get_bool_value(&self, key: &str, user: Option<User>, default: bool) -> bool {
self.get_bool_details(key, user, default).await.value
}

/// The same as [`Client::get_bool_value`] but returns an [`EvaluationDetails`] which
/// contains additional information about the evaluation process.
///
/// # Examples
///
/// ```no_run
/// use configcat::{Client, User};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new("SDK_KEY").unwrap();
///
/// let user = User::new("user-id");
/// let details = client.get_bool_details("flag-key", Some(user), false).await;
/// }
/// ```
pub async fn get_bool_details(
&self,
key: &str,
Expand All @@ -62,7 +147,8 @@ impl Client {
value: val,
key: key.to_owned(),
user,
..EvaluationDetails::from_results(eval_result, &result)
fetch_time: Some(*result.fetch_time()),
..eval_result.into()
},
None => {
let err = ClientError::new(ErrorKind::SettingValueTypeMismatch, format!("The type of a setting must match the requested type. Setting's type was '{}' but the requested type was 'bool'. Learn more: https://configcat.com/docs/sdk-reference/rust/#setting-type-mapping", eval_result.setting_type));
Expand All @@ -77,6 +163,23 @@ impl Client {
}
}

/// Evaluate a feature flag identified by the given `key`.
///
/// Returns an [`EvaluationDetails`] that contains the evaluated feature flag's value in a [`Value`] variant.
///
/// # Examples
///
/// ```no_run
/// use configcat::{Client, User};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new("SDK_KEY").unwrap();
///
/// let user = User::new("user-id");
/// let value = client.get_flag_details("flag-key", Some(user)).await;
/// }
/// ```
pub async fn get_flag_details(
&self,
key: &str,
Expand Down
3 changes: 2 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include!(concat!(env!("OUT_DIR"), "/built.rs"));
/// The ConfigCat Rust SDK's version.
pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");

pub const SDK_KEY_PROXY_PREFIX: &str = "configcat-proxy/";
pub const CONFIG_FILE_NAME: &str = "config_v6.json";
Expand Down
33 changes: 25 additions & 8 deletions src/eval/details.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
use crate::eval::evaluator::EvalResult;
use crate::fetch::service::ConfigResult;
use crate::{ClientError, PercentageOption, TargetingRule, User};
use chrono::{DateTime, Utc};
use std::sync::Arc;

/// Details of the flag evaluation's result.
///
/// # Examples
///
/// ```no_run
/// use configcat::{Client, User};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new("SDK_KEY").unwrap();
///
/// let user = User::new("user-id");
/// let details = client.get_bool_details("flag-key", Some(user), false).await;
///
/// let flag_val = details.value;
/// let fetch_time = details.fetch_time.unwrap();
/// }
/// ```
#[derive(Default)]
pub struct EvaluationDetails<T> {
pub value: T,
Expand All @@ -18,7 +34,7 @@ pub struct EvaluationDetails<T> {
pub user: Option<User>,
/// Error in case evaluation failed.
pub error: Option<ClientError>,
/// Time of last successful config download.
/// Time of last successful config download on which the evaluation was based.
pub fetch_time: Option<DateTime<Utc>>,
/// The targeting rule (if any) that matched during the evaluation and was used to return the evaluated value.
pub matched_targeting_rule: Option<Arc<TargetingRule>>,
Expand All @@ -37,13 +53,14 @@ impl<T: Default> EvaluationDetails<T> {
..EvaluationDetails::default()
}
}
}

pub(crate) fn from_results(eval_result: EvalResult, config_result: &ConfigResult) -> Self {
Self {
variation_id: eval_result.variation_id,
fetch_time: Some(*config_result.fetch_time()),
matched_targeting_rule: eval_result.rule,
matched_percentage_option: eval_result.option,
impl<T: Default> From<EvalResult> for EvaluationDetails<T> {
fn from(value: EvalResult) -> Self {
EvaluationDetails {
variation_id: value.variation_id,
matched_targeting_rule: value.rule,
matched_percentage_option: value.option,
..EvaluationDetails::default()
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/eval/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub fn eval(
let mut eval_log = EvalLogBuilder::default();
let mut cycle_tracker = Vec::<String>::default();
if eval_log_enabled!() {
eval_log.append(format!("Evaluation '{key}'").as_str());
eval_log.append(format!("Evaluating '{key}'").as_str());
if let Some(user) = user {
eval_log.append(format!(" for User '{user}'").as_str());
}
Expand Down Expand Up @@ -561,15 +561,15 @@ fn eval_segment_cond(
}
log.append(format!("{user_condition}").as_str());
}
result = eval_user_cond(user_condition, key, user, salt, &segment.name);
result = eval_user_cond(user_condition, key, user, salt, segment.name.as_str());
if eval_log_enabled!() {
let end = if result.is_match() {
""
} else {
", skipping the remaining AND conditions"
};
let match_msg = format!("{}", result.is_match());
log.append(" = >")
log.append(" => ")
.append(match_msg.as_str())
.append(end)
.dec_indent();
Expand Down Expand Up @@ -597,7 +597,7 @@ fn eval_segment_cond(
log.append(" failed to evaluate.");
} else {
let msg = format!("{}", result.is_match() == needs_true);
log.append(format!("evaluates to {msg}.").as_str());
log.append(format!(" evaluates to {msg}.").as_str());
}
log.dec_indent().new_ln(Some(")"));
}
Expand Down Expand Up @@ -845,7 +845,7 @@ fn eval_starts_ends_with(
} else {
return Fatal(SALT_MISSING_MSG.to_owned());
};
let parts = item.split('_').collect::<Vec<&str>>();
let parts: Vec<&str> = item.split('_').collect();
if parts.len() < 2 || parts[1].is_empty() {
return Fatal(COMP_VAL_INVALID_MSG.to_owned());
}
Expand Down Expand Up @@ -904,7 +904,7 @@ fn eval_semver_is_one_of(
if trimmed.is_empty() {
continue;
}
let comp_ver = if let Ok(ver) = Version::parse(trimmed) {
let comp_ver = if let Ok(ver) = utils::parse_semver(trimmed) {
ver
} else {
// NOTE: Previous versions of the evaluation algorithm ignored invalid comparison values.
Expand All @@ -923,7 +923,7 @@ fn eval_semver_compare(
user_val: Version,
comp: &UserComparator,
) -> ConditionResult {
let comp_ver = if let Ok(ver) = Version::parse(comp_val) {
let comp_ver = if let Ok(ver) = utils::parse_semver(comp_val) {
ver
} else {
// NOTE: Previous versions of the evaluation algorithm ignored invalid comparison values.
Expand Down
17 changes: 11 additions & 6 deletions src/eval/log_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,25 @@ impl EvalLogBuilder {
}
builder.append("THEN");
if let Some(sv) = rule_srv_value.as_ref() {
builder.append(format!("{}", sv.value).as_str());
builder.append(format!(" {}", sv.value).as_str());
} else {
builder.append(" % options");
}
return match result {
builder.append(" => ");
match result {
ConditionResult::Success(matched) => {
if *matched {
builder.append("MATCH, applying rule")
builder.append("MATCH, applying rule");
} else {
builder.append("no match")
builder.append("no match");
}
}
_ => builder.append(format!("{result}").as_str()),
};
_ => {
builder.append(format!("{result}").as_str());
}
}
builder.dec_indent();
builder
}

pub fn content(&self) -> &str {
Expand Down
Loading

0 comments on commit 7e233d0

Please sign in to comment.