Skip to content

Commit

Permalink
Add user object / missing docs
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed May 7, 2024
1 parent 1c8b46f commit 9c4e43d
Show file tree
Hide file tree
Showing 13 changed files with 547 additions and 64 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tokio-util = "0.7"
lazy_static = "1.4"
sha1 = "0.10"
base16ct = { version = "0.2", features = ["alloc"] }
semver = "1.0"

[build-dependencies]
built = "0.7"
Expand Down
9 changes: 7 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::options::{Options, OptionsBuilder, OptionsError};
use crate::options::{Options, OptionsBuilder};
use crate::ClientError;
use std::sync::Arc;

pub struct Client {
options: Arc<Options>,

Check warning on line 6 in src/client.rs

View workflow job for this annotation

GitHub Actions / test

field `options` is never read

Check failure on line 6 in src/client.rs

View workflow job for this annotation

GitHub Actions / clippy

field `options` is never read

Check failure on line 6 in src/client.rs

View workflow job for this annotation

GitHub Actions / clippy

field `options` is never read

Check warning on line 6 in src/client.rs

View workflow job for this annotation

GitHub Actions / test

field `options` is never read
}

impl Client {
pub fn from_builder(builder: OptionsBuilder) -> Result<Self, OptionsError> {
pub fn from_builder(builder: OptionsBuilder) -> Result<Self, ClientError> {
let result = builder.build();
match result {
Ok(opts) => Ok(Client::new(opts)),
Expand All @@ -19,4 +20,8 @@ impl Client {
options: Arc::new(options),
}
}

pub fn refresh() -> Result<(), ClientError> {
Ok(())
}
}
12 changes: 10 additions & 2 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ use thiserror::Error;

#[derive(Error, PartialEq, Debug)]
pub enum ClientError {
#[error("SDK key is invalid. ({0})")]
InvalidSdkKey(String),
#[error("{0}")]
Fetch(String),
}

#[derive(Error, PartialEq, Debug)]
pub enum InternalError {
#[error("JSON parsing failed. ({0})")]
Parse(String),

Check warning on line 14 in src/errors.rs

View workflow job for this annotation

GitHub Actions / test

variants `Parse` and `Http` are never constructed

Check failure on line 14 in src/errors.rs

View workflow job for this annotation

GitHub Actions / clippy

variants `Parse` and `Http` are never constructed

Check failure on line 14 in src/errors.rs

View workflow job for this annotation

GitHub Actions / clippy

variants `Parse` and `Http` are never constructed

Check warning on line 14 in src/errors.rs

View workflow job for this annotation

GitHub Actions / test

variants `Parse` and `Http` are never constructed
#[error("{1}")]
Http(i64, String),
#[error("{0}")]
Http(String),
}
38 changes: 38 additions & 0 deletions src/eval/evaluator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::UserCondition;
use std::fmt::{Display, Formatter};

enum ConditionResult {
Ok(bool),
NoUser,
AttrMissing(UserCondition),
AttrInvalid(String, UserCondition),
CompValInvalid(Option<String>),
Fatal(String),
}

impl Display for ConditionResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ConditionResult::Ok(_) => f.write_str(""),
ConditionResult::NoUser => f.write_str("cannot evaluate, User Object is missing"),
ConditionResult::AttrMissing(cond) => write!(
f,
"cannot evaluate, the User.{} attribute is missing",
cond.fmt_comp_attr()
),
ConditionResult::AttrInvalid(reason, cond) => write!(
f,
"cannot evaluate, the User.{} attribute is invalid ({})",
cond.fmt_comp_attr(),
reason
),
ConditionResult::CompValInvalid(err) => write!(
f,
"cannot evaluate, ({})",
err.as_ref()
.unwrap_or(&"comparison value is missing or invalid".to_owned())
),
ConditionResult::Fatal(err) => write!(f, "cannot evaluate ({})", err),
}
}
}
1 change: 1 addition & 0 deletions src/eval/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod evaluator;
30 changes: 15 additions & 15 deletions src/fetch/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use chrono::Utc;
use reqwest::header::{HeaderMap, ETAG, IF_NONE_MATCH};

use crate::constants::{CONFIG_FILE_NAME, PKG_VERSION, SDK_KEY_PROXY_PREFIX};
use crate::errors::ClientError;
use crate::errors::InternalError;
use crate::fetch::fetcher::FetchResponse::{Failed, Fetched, NotModified};
use crate::model::config::{entry_from_json, ConfigEntry};
use crate::model::enums::RedirectMode;
Expand All @@ -16,7 +16,7 @@ const CONFIGCAT_UA_HEADER: &str = "X-ConfigCat-UserAgent";
pub enum FetchResponse {
Fetched(ConfigEntry),
NotModified,
Failed(ClientError, bool),
Failed(InternalError, bool),
}

impl FetchResponse {
Expand Down Expand Up @@ -45,7 +45,7 @@ impl Fetcher {
.unwrap(),
);
Self {
sdk_key: sdk_key.to_string(),
sdk_key: sdk_key.to_owned(),
fetch_url: Arc::new(Mutex::new(url)),
is_custom_url: is_custom,
http_client: reqwest::Client::builder()
Expand Down Expand Up @@ -94,9 +94,9 @@ impl Fetcher {
_ => return response,
}
}
let msg = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support".to_string();
let msg = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support".to_owned();
log_err!(event_id: 1104, "{}", msg);
Failed(ClientError::Http(1104, msg), true)
Failed(InternalError::Http(msg), true)
}

async fn fetch_http(&self, url: &str, etag: &str) -> FetchResponse {
Expand All @@ -107,7 +107,7 @@ impl Fetcher {
);
let mut builder = self.http_client.get(final_url);
if !etag.is_empty() {
builder = builder.header(IF_NONE_MATCH, etag.to_string());
builder = builder.header(IF_NONE_MATCH, etag.to_owned());
}

let result = builder.send().await;
Expand All @@ -131,14 +131,14 @@ impl Fetcher {
Err(parse_error) => {
let msg = format!("Fetching config JSON was successful but the HTTP response content was invalid. {parse_error}");
log_err!(event_id: 1105, "{}", msg);
Failed(ClientError::Http(1105, msg), true)
Failed(InternalError::Http(msg), true)
}
}
}
Err(body_error) => {
let msg = format!("Fetching config JSON was successful but the HTTP response content was invalid. {body_error}");
log_err!(event_id: 1105, "{}", msg);
Failed(ClientError::Http(1105, msg), true)
Failed(InternalError::Http(msg), true)
}
}
}
Expand All @@ -149,31 +149,31 @@ impl Fetcher {
code @ 404 | code @ 403 => {
let msg = format!("Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey. Status code: {code}");
log_err!(event_id: 1100, "{}", msg);
Failed(ClientError::Http(1100, msg), false)
Failed(InternalError::Http(msg), false)
}
code => {
let msg = format!("Unexpected HTTP response was received while trying to fetch config JSON. Status code: {code}");
log_err!(event_id: 1101, "{}", msg);
Failed(ClientError::Http(1101, msg), true)
Failed(InternalError::Http(msg), true)
}
},
Err(error) => {
if error.is_timeout() {
let msg = "Request timed out while trying to fetch config JSON.".to_string();
let msg = "Request timed out while trying to fetch config JSON.".to_owned();
log_err!(event_id: 1102, "{}", msg);
Failed(ClientError::Http(1102, msg), true)
Failed(InternalError::Http(msg), true)
} else {
let msg = format!("Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) over HTTP. {error}");
log_err!(event_id: 1103, "{}", msg);
Failed(ClientError::Http(1103, msg), true)
Failed(InternalError::Http(msg), true)
}
}
}
}

fn fetch_url(&self) -> String {
let url = self.fetch_url.lock().unwrap();
url.to_string()
url.to_owned()
}

fn set_fetch_url(&self, new_url: String) {
Expand Down Expand Up @@ -572,7 +572,7 @@ mod data_governance_tests {
}

fn format_body(url: String, redirect_mode: u8) -> String {
return "{ \"p\": { \"u\": \"".to_string()
return "{ \"p\": { \"u\": \"".to_owned()
+ url.as_str()
+ "\", \"r\": "
+ redirect_mode.to_string().as_str()
Expand Down
39 changes: 21 additions & 18 deletions src/fetch/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ use crate::options::Options;
use crate::utils::sha1;

pub enum ServiceResult {
Ok(Arc<Config>),
Failed(ClientError, Arc<Config>),
Ok(Arc<Config>, DateTime<Utc>),
Failed(ClientError, Arc<Config>, DateTime<Utc>),
}

impl ServiceResult {
pub fn config(&self) -> &Arc<Config> {
match self {
ServiceResult::Ok(entry) => entry,
ServiceResult::Failed(_, entry) => entry,
ServiceResult::Ok(entry, _) => entry,
ServiceResult::Failed(_, entry, _) => entry,
}
}
}
Expand Down Expand Up @@ -63,13 +63,12 @@ impl ConfigService {
sdk_key = opts.sdk_key()
)),
fetcher: Fetcher::new(
opts.base_url().clone().unwrap_or_else(|| {
if *opts.data_governance() == DataGovernance::Global {
GLOBAL_CDN_URL.to_string()
} else {
EU_CDN_URL.to_string()
}
}),
opts.base_url()
.clone()
.unwrap_or_else(|| match *opts.data_governance() {
DataGovernance::Global => GLOBAL_CDN_URL.to_owned(),
DataGovernance::EU => EU_CDN_URL.to_owned(),
}),
!opts.base_url().is_none(),
opts.sdk_key(),
opts.polling_mode().mode_identifier(),
Expand Down Expand Up @@ -109,8 +108,8 @@ impl ConfigService {
let result =
fetch_if_older(&self.state, &self.options, DateTime::<Utc>::MAX_UTC, false).await;
match result {
ServiceResult::Ok(_) => Ok(()),
ServiceResult::Failed(err, _) => Err(err),
ServiceResult::Ok(_, _) => Ok(()),
ServiceResult::Failed(err, _, _) => Err(err),
}
}

Expand Down Expand Up @@ -158,7 +157,7 @@ async fn fetch_if_older(

if entry.fetch_time > threshold || state.offline.load(Ordering::SeqCst) || prefer_cached {
state.initialized();
return ServiceResult::Ok(entry.config.clone());
return ServiceResult::Ok(entry.config.clone(), entry.fetch_time);
}

let response = state.fetcher.fetch(&entry.etag).await;
Expand All @@ -169,14 +168,14 @@ async fn fetch_if_older(
options
.cache()
.write(&state.cache_key, entry.serialize().as_str());
ServiceResult::Ok(entry.config.clone())
ServiceResult::Ok(entry.config.clone(), entry.fetch_time)
}
FetchResponse::NotModified => {
*entry = entry.with_time(Utc::now());
options
.cache()
.write(&state.cache_key, entry.serialize().as_str());
ServiceResult::Ok(entry.config.clone())
ServiceResult::Ok(entry.config.clone(), entry.fetch_time)
}
FetchResponse::Failed(err, transient) => {
if !transient && !entry.is_empty() {
Expand All @@ -185,7 +184,11 @@ async fn fetch_if_older(
.cache()
.write(&state.cache_key, entry.serialize().as_str());
}
ServiceResult::Failed(err, entry.config.clone())
ServiceResult::Failed(
ClientError::Fetch(err.to_string()),
entry.config.clone(),
entry.fetch_time,
)
}
}
}
Expand Down Expand Up @@ -644,7 +647,7 @@ mod service_tests {

fn write(&self, _: &str, value: &str) {
let mut val = self.val.lock().unwrap();
*val = value.to_string()
*val = value.to_owned()
}
}
}
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ mod cache;
mod client;
mod constants;
mod errors;
mod eval;
mod fetch;
mod model;
mod modes;
mod options;
mod user;
mod utils;

pub use cache::ConfigCache;
pub use client::Client;
pub use errors::ClientError;
pub use model::config::{
Condition, PercentageOption, PrerequisiteFlagCondition, Segment, SegmentCondition, ServedValue,
Setting, TargetingRule, UserCondition,
Setting, SettingValue, TargetingRule, UserCondition,
};
pub use model::enums::{
PrerequisiteFlagComparator, SegmentComparator, SettingType, UserComparator,
DataGovernance, PrerequisiteFlagComparator, SegmentComparator, SettingType, UserComparator,
};
pub use modes::PollingMode;
pub use options::{Options, OptionsBuilder};
pub use user::{User, UserValue};
Loading

0 comments on commit 9c4e43d

Please sign in to comment.