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
#81)

* refactor: split precomputed assignments between eppo-core and fastly-edge-assignments server.

* reset

* rename method

* part of evaluator

* EvaluationError

* simplify no error

* eppo_core 5.1.0

* compile and simplify

* fix spec

* refactor(core): move PrecomputedConfiguration into core (#110)

* Revert "eppo_core 5.1.0"

This reverts commit 9621c51.

---------

Co-authored-by: Oleksii Shmalko <[email protected]>
  • Loading branch information
leoromanovsky and rasendubi authored Dec 6, 2024
1 parent 9721a38 commit 9c74cfa
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 174 deletions.
167 changes: 167 additions & 0 deletions eppo_core/src/eval/eval_precomputed_assignments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::collections::HashMap;
use std::sync::Arc;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::eval::get_assignment;
use crate::ufc::{Assignment, AssignmentFormat, Environment, ValueWire, VariationType};
use crate::{Attributes, Configuration, Str};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrecomputedConfiguration {
created_at: DateTime<Utc>,
/// `format` is always `AssignmentFormat::Precomputed`.
format: AssignmentFormat,
// Environment might be missing if configuration was absent during evaluation.
environment: Option<Environment>,
flags: HashMap<String, PrecomputedAssignment>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PrecomputedAssignment {
variation_type: VariationType,
variation_value: ValueWire,

do_log: bool,
// If `do_log` is false, the client doesn’t need the field below.
#[serde(default, skip_serializing_if = "Option::is_none")]
allocation_key: Option<Str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
variation_key: Option<Str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
extra_logging: Option<HashMap<String, String>>,
}

impl From<Assignment> for PrecomputedAssignment {
fn from(assignment: Assignment) -> PrecomputedAssignment {
match assignment.event {
Some(event) => PrecomputedAssignment {
variation_type: assignment.value.variation_type(),
variation_value: assignment.value.variation_value(),
do_log: true,
allocation_key: Some(event.base.allocation.clone()),
variation_key: Some(event.base.variation.clone()),
extra_logging: Some(event.base.extra_logging.clone()),
},
None => PrecomputedAssignment {
variation_type: assignment.value.variation_type(),
variation_value: assignment.value.variation_value(),
do_log: false,
allocation_key: None,
variation_key: None,
extra_logging: None,
},
}
}
}

pub fn get_precomputed_assignments(
configuration: Option<&Configuration>,
subject_key: &Str,
subject_attributes: &Arc<Attributes>,
now: DateTime<Utc>,
) -> PrecomputedConfiguration {
let Some(configuration) = configuration else {
log::warn!(target: "eppo",
subject_key;
"evaluating a flag before Eppo configuration has been fetched");
return PrecomputedConfiguration {
created_at: now,
format: AssignmentFormat::Precomputed,
environment: None,
flags: HashMap::new(),
};
};

let mut flags = HashMap::new();

for key in configuration.flags.compiled.flags.keys() {
match get_assignment(
Some(configuration),
key,
&subject_key,
&subject_attributes,
None,
now,
) {
Ok(Some(assignment)) => {
flags.insert(key.clone(), assignment.into());
}
Ok(None) => {}
Err(e) => {
eprintln!("Failed to evaluate assignment for key {}: {:?}", key, e);
}
}
}

log::trace!(target: "eppo",
subject_key,
assignments:serde = flags;
"evaluated precomputed assignments");
PrecomputedConfiguration {
created_at: now,
format: AssignmentFormat::Precomputed,
environment: Some(configuration.flags.compiled.environment.clone()),
flags,
}
}

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

use crate::{
eval::get_precomputed_assignments, ufc::UniversalFlagConfig, 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 subject_key = "test-subject-1".into();
let subject_attributes = Arc::new(Attributes::new());
let now = Utc::now();

// Get precomputed assignments
let precomputed = get_precomputed_assignments(
Some(&configuration),
&subject_key,
&subject_attributes,
now,
);

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
);
}
}
}
17 changes: 16 additions & 1 deletion 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_assignments::PrecomputedConfiguration,
events::AssignmentEvent,
ufc::{Assignment, AssignmentValue, VariationType},
Attributes, Configuration, ContextAttributes, EvaluationError, SdkMetadata, Str,
Expand All @@ -12,7 +13,7 @@ use crate::{
use super::{
eval_details::{EvaluationDetails, EvaluationResultWithDetails},
get_assignment, get_assignment_details, get_bandit_action, get_bandit_action_details,
BanditResult,
get_precomputed_assignments, BanditResult,
};

pub struct EvaluatorConfig {
Expand Down Expand Up @@ -112,6 +113,20 @@ impl Evaluator {
)
}

pub fn get_precomputed_assignments(
&self,
subject_key: &Str,
subject_attributes: &Arc<Attributes>,
) -> PrecomputedConfiguration {
let configuration = self.get_configuration();
get_precomputed_assignments(
configuration.as_ref().map(AsRef::as_ref),
&subject_key,
&subject_attributes,
Utc::now(),
)
}

fn get_configuration(&self) -> Option<Arc<Configuration>> {
self.config.configuration_store.get_configuration()
}
Expand Down
2 changes: 2 additions & 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_assignments;
mod eval_rules;
mod eval_visitor;
mod evaluator;
Expand All @@ -9,4 +10,5 @@ pub mod eval_details;

pub use eval_assignment::{get_assignment, get_assignment_details};
pub use eval_bandits::{get_bandit_action, get_bandit_action_details, BanditResult};
pub use eval_precomputed_assignments::{get_precomputed_assignments, PrecomputedConfiguration};
pub use evaluator::{Evaluator, EvaluatorConfig};
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 9c74cfa

Please sign in to comment.