Skip to content

Commit

Permalink
refactor: split precomputed assignments between eppo-core and fastly-…
Browse files Browse the repository at this point in the history
…edge-assignments server.
  • Loading branch information
leoromanovsky committed Nov 28, 2024
1 parent 3d19959 commit 40b83fe
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 132 deletions.
2 changes: 1 addition & 1 deletion eppo_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub enum EvaluationError {
/// default assignment.
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum EvaluationFailure {
pub enum EvaluationFailure {
/// True evaluation error that should be returned to the user.
#[error(transparent)]
Error(EvaluationError),
Expand Down
114 changes: 114 additions & 0 deletions eppo_core/src/eval/eval_precomputed_assignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::collections::HashMap;

use crate::{error::EvaluationFailure, ufc::Assignment};

#[derive(Debug)]
pub struct PrecomputedConfiguration {
pub flags: HashMap<String, Result<Assignment, EvaluationFailure>>,
}

#[cfg(test)]
mod tests {
use chrono::Utc;

use crate::{
configuration_store::ConfigurationStore,
eval::{Evaluator, EvaluatorConfig},
ufc::{UniversalFlagConfig, VariationType},
Attributes, Configuration, SdkMetadata,
};
use std::{fs, sync::Arc};

fn setup_test_config() -> Configuration {
let _ = env_logger::builder().is_test(true).try_init();

// Load test configuration
let ufc_config = UniversalFlagConfig::from_json(
SdkMetadata {
name: "test",
version: "0.1.0",
},
fs::read("../sdk-test-data/ufc/flags-v1.json").unwrap(),
)
.unwrap();
Configuration::from_server_response(ufc_config, None)
}

#[test]
fn test_precomputed_assignment_basic() {
let configuration = setup_test_config();

let configuration_store = Arc::new(ConfigurationStore::new());
configuration_store.set_configuration(Arc::new(configuration));

let evaluator = Evaluator::new(EvaluatorConfig {
configuration_store: configuration_store.clone(),
sdk_metadata: SdkMetadata {
name: "test",
version: "0.1.0",
},
});

let subject_key = "test-subject-1".into();
let subject_attributes = Arc::new(Attributes::new());
let now = Utc::now();

// Get precomputed assignments
let precomputed =
evaluator.get_precomputed_assignment(&subject_key, &subject_attributes, false);

assert!(
!precomputed.flags.is_empty(),
"Should have precomputed flags"
);

// Each flag in the configuration should have an entry
for flag_key in precomputed.flags.keys() {
assert!(
precomputed.flags.contains_key(flag_key),
"Should have precomputed assignment for flag {}",
flag_key
);
}
}

#[test]
fn test_precomputed_assignment_early_exit() {
let mut configuration = setup_test_config();
let num_good_flags = configuration.flags.compiled.flags.len();

// Add a flag that will cause an evaluation error
configuration.flags.compiled.flags.insert(
"error-flag".to_string(),
Ok(crate::ufc::Flag {
variation_type: VariationType::String,
allocations: vec![].into_boxed_slice(),
}),
);

let configuration_store = Arc::new(ConfigurationStore::new());
configuration_store.set_configuration(Arc::new(configuration));

let evaluator = Evaluator::new(EvaluatorConfig {
configuration_store: configuration_store.clone(),
sdk_metadata: SdkMetadata {
name: "test",
version: "0.1.0",
},
});

let subject_key = "test-subject-1".into();
let subject_attributes = Arc::new(Attributes::new());
let now = Utc::now();

// Get assignments with early exit
let precomputed_with_early_exit =
evaluator.get_precomputed_assignment(&subject_key, &subject_attributes, true);

// Verify we have fewer entries due to early exit
assert!(
precomputed_with_early_exit.flags.len() < num_good_flags,
"Early exit should stop processing on first error"
);
}
}
31 changes: 31 additions & 0 deletions eppo_core/src/eval/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use chrono::Utc;

use crate::{
configuration_store::ConfigurationStore,
eval::eval_precomputed_assignment::PrecomputedConfiguration,
events::AssignmentEvent,
ufc::{Assignment, AssignmentValue, VariationType},
Attributes, Configuration, ContextAttributes, EvaluationError, SdkMetadata, Str,
Expand Down Expand Up @@ -112,6 +113,36 @@ impl Evaluator {
)
}

pub fn get_precomputed_assignment(
&self,
subject_key: &Str,
subject_attributes: &Arc<Attributes>,
early_exit: bool,
) -> PrecomputedConfiguration {
let config = self.get_configuration();

let mut flags = HashMap::new();

if let Some(config) = config {
for key in config.flags.compiled.flags.keys() {
match self.get_assignment(key, &subject_key, &subject_attributes, None) {
Ok(Some(assignment)) => {
flags.insert(key.clone(), Ok(assignment));
}
Ok(None) => continue,
Err(e) => {
eprintln!("Failed to evaluate assignment for key {}: {:?}", key, e);
if early_exit {
break;
}
}
}
}
}

PrecomputedConfiguration { flags }
}

fn get_configuration(&self) -> Option<Arc<Configuration>> {
self.config.configuration_store.get_configuration()
}
Expand Down
1 change: 1 addition & 0 deletions eppo_core/src/eval/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod eval_assignment;
mod eval_bandits;
mod eval_details_builder;
mod eval_precomputed_assignment;
mod eval_rules;
mod eval_visitor;
mod evaluator;
Expand Down
1 change: 0 additions & 1 deletion eppo_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ pub mod configuration_store;
pub mod eval;
pub mod events;
pub mod poller_thread;
pub mod precomputed_assignments;
#[cfg(feature = "pyo3")]
pub mod pyo3;
pub mod sharder;
Expand Down
86 changes: 0 additions & 86 deletions eppo_core/src/precomputed_assignments.rs

This file was deleted.

Loading

0 comments on commit 40b83fe

Please sign in to comment.