diff --git a/Cargo.lock b/Cargo.lock index 3e5c8a95a..2791f7fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5574,7 +5574,6 @@ dependencies = [ "reqwest", "rs-snowflake", "serde", - "serde_json", "service_utils", "superposition_derives", "superposition_macros", diff --git a/clients/java/bindings/src/main/kotlin/uniffi/superposition_client/superposition_client.kt b/clients/java/bindings/src/main/kotlin/uniffi/superposition_client/superposition_client.kt index d5b25ad86..423167afe 100644 --- a/clients/java/bindings/src/main/kotlin/uniffi/superposition_client/superposition_client.kt +++ b/clients/java/bindings/src/main/kotlin/uniffi/superposition_client/superposition_client.kt @@ -37,6 +37,7 @@ import uniffi.superposition_types.Config import uniffi.superposition_types.Context import uniffi.superposition_types.DimensionInfo import uniffi.superposition_types.ExperimentStatusType +import uniffi.superposition_types.ExtendedMap import uniffi.superposition_types.FfiConverterTypeBucket import uniffi.superposition_types.FfiConverterTypeBuckets import uniffi.superposition_types.FfiConverterTypeCondition @@ -44,6 +45,7 @@ import uniffi.superposition_types.FfiConverterTypeConfig import uniffi.superposition_types.FfiConverterTypeContext import uniffi.superposition_types.FfiConverterTypeDimensionInfo import uniffi.superposition_types.FfiConverterTypeExperimentStatusType +import uniffi.superposition_types.FfiConverterTypeExtendedMap import uniffi.superposition_types.FfiConverterTypeGroupType import uniffi.superposition_types.FfiConverterTypeMergeStrategy import uniffi.superposition_types.FfiConverterTypeOverrides @@ -61,6 +63,7 @@ import uniffi.superposition_types.RustBuffer as RustBufferConfig import uniffi.superposition_types.RustBuffer as RustBufferContext import uniffi.superposition_types.RustBuffer as RustBufferDimensionInfo import uniffi.superposition_types.RustBuffer as RustBufferExperimentStatusType +import uniffi.superposition_types.RustBuffer as RustBufferExtendedMap import uniffi.superposition_types.RustBuffer as RustBufferGroupType import uniffi.superposition_types.RustBuffer as RustBufferMergeStrategy import uniffi.superposition_types.RustBuffer as RustBufferOverrides @@ -824,9 +827,9 @@ internal interface UniffiLib : Library { } // FFI functions - fun uniffi_superposition_core_fn_func_ffi_eval_config(`defaultConfig`: RustBuffer.ByValue,`contexts`: RustBuffer.ByValue,`overrides`: RustBuffer.ByValue,`dimensions`: RustBuffer.ByValue,`queryData`: RustBuffer.ByValue,`mergeStrategy`: RustBufferMergeStrategy.ByValue,`filterPrefixes`: RustBuffer.ByValue,`experimentation`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + fun uniffi_superposition_core_fn_func_ffi_eval_config(`defaultConfig`: RustBufferExtendedMap.ByValue,`contexts`: RustBuffer.ByValue,`overrides`: RustBuffer.ByValue,`dimensions`: RustBuffer.ByValue,`queryData`: RustBuffer.ByValue,`mergeStrategy`: RustBufferMergeStrategy.ByValue,`filterPrefixes`: RustBuffer.ByValue,`experimentation`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue -fun uniffi_superposition_core_fn_func_ffi_eval_config_with_reasoning(`defaultConfig`: RustBuffer.ByValue,`contexts`: RustBuffer.ByValue,`overrides`: RustBuffer.ByValue,`dimensions`: RustBuffer.ByValue,`queryData`: RustBuffer.ByValue,`mergeStrategy`: RustBufferMergeStrategy.ByValue,`filterPrefixes`: RustBuffer.ByValue,`experimentation`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +fun uniffi_superposition_core_fn_func_ffi_eval_config_with_reasoning(`defaultConfig`: RustBufferExtendedMap.ByValue,`contexts`: RustBuffer.ByValue,`overrides`: RustBuffer.ByValue,`dimensions`: RustBuffer.ByValue,`queryData`: RustBuffer.ByValue,`mergeStrategy`: RustBufferMergeStrategy.ByValue,`filterPrefixes`: RustBuffer.ByValue,`experimentation`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue fun uniffi_superposition_core_fn_func_ffi_get_applicable_variants(`eargs`: RustBuffer.ByValue,`dimensionsInfo`: RustBuffer.ByValue,`queryData`: RustBuffer.ByValue,`prefix`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue @@ -958,10 +961,10 @@ private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) { } @Suppress("UNUSED_PARAMETER") private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { - if (lib.uniffi_superposition_core_checksum_func_ffi_eval_config() != 61169.toShort()) { + if (lib.uniffi_superposition_core_checksum_func_ffi_eval_config() != 40327.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_superposition_core_checksum_func_ffi_eval_config_with_reasoning() != 47981.toShort()) { + if (lib.uniffi_superposition_core_checksum_func_ffi_eval_config_with_reasoning() != 10312.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_superposition_core_checksum_func_ffi_get_applicable_variants() != 58234.toShort()) { @@ -1720,21 +1723,23 @@ public object FfiConverterMapStringTypeOverrides: FfiConverterRustBuffer, `contexts`: List, `overrides`: Map, `dimensions`: Map, `queryData`: Map, `mergeStrategy`: MergeStrategy, `filterPrefixes`: List?, `experimentation`: ExperimentationArgs?): Map { + + + @Throws(OperationException::class) fun `ffiEvalConfig`(`defaultConfig`: ExtendedMap, `contexts`: List, `overrides`: Map, `dimensions`: Map, `queryData`: Map, `mergeStrategy`: MergeStrategy, `filterPrefixes`: List?, `experimentation`: ExperimentationArgs?): Map { return FfiConverterMapStringString.lift( uniffiRustCallWithError(OperationException) { _status -> UniffiLib.INSTANCE.uniffi_superposition_core_fn_func_ffi_eval_config( - FfiConverterMapStringString.lower(`defaultConfig`),FfiConverterSequenceTypeContext.lower(`contexts`),FfiConverterMapStringTypeOverrides.lower(`overrides`),FfiConverterMapStringTypeDimensionInfo.lower(`dimensions`),FfiConverterMapStringString.lower(`queryData`),FfiConverterTypeMergeStrategy.lower(`mergeStrategy`),FfiConverterOptionalSequenceString.lower(`filterPrefixes`),FfiConverterOptionalTypeExperimentationArgs.lower(`experimentation`),_status) + FfiConverterTypeExtendedMap.lower(`defaultConfig`),FfiConverterSequenceTypeContext.lower(`contexts`),FfiConverterMapStringTypeOverrides.lower(`overrides`),FfiConverterMapStringTypeDimensionInfo.lower(`dimensions`),FfiConverterMapStringString.lower(`queryData`),FfiConverterTypeMergeStrategy.lower(`mergeStrategy`),FfiConverterOptionalSequenceString.lower(`filterPrefixes`),FfiConverterOptionalTypeExperimentationArgs.lower(`experimentation`),_status) } ) } - @Throws(OperationException::class) fun `ffiEvalConfigWithReasoning`(`defaultConfig`: Map, `contexts`: List, `overrides`: Map, `dimensions`: Map, `queryData`: Map, `mergeStrategy`: MergeStrategy, `filterPrefixes`: List?, `experimentation`: ExperimentationArgs?): Map { + @Throws(OperationException::class) fun `ffiEvalConfigWithReasoning`(`defaultConfig`: ExtendedMap, `contexts`: List, `overrides`: Map, `dimensions`: Map, `queryData`: Map, `mergeStrategy`: MergeStrategy, `filterPrefixes`: List?, `experimentation`: ExperimentationArgs?): Map { return FfiConverterMapStringString.lift( uniffiRustCallWithError(OperationException) { _status -> UniffiLib.INSTANCE.uniffi_superposition_core_fn_func_ffi_eval_config_with_reasoning( - FfiConverterMapStringString.lower(`defaultConfig`),FfiConverterSequenceTypeContext.lower(`contexts`),FfiConverterMapStringTypeOverrides.lower(`overrides`),FfiConverterMapStringTypeDimensionInfo.lower(`dimensions`),FfiConverterMapStringString.lower(`queryData`),FfiConverterTypeMergeStrategy.lower(`mergeStrategy`),FfiConverterOptionalSequenceString.lower(`filterPrefixes`),FfiConverterOptionalTypeExperimentationArgs.lower(`experimentation`),_status) + FfiConverterTypeExtendedMap.lower(`defaultConfig`),FfiConverterSequenceTypeContext.lower(`contexts`),FfiConverterMapStringTypeOverrides.lower(`overrides`),FfiConverterMapStringTypeDimensionInfo.lower(`dimensions`),FfiConverterMapStringString.lower(`queryData`),FfiConverterTypeMergeStrategy.lower(`mergeStrategy`),FfiConverterOptionalSequenceString.lower(`filterPrefixes`),FfiConverterOptionalTypeExperimentationArgs.lower(`experimentation`),_status) } ) } diff --git a/clients/python/bindings/superposition_bindings/superposition_client.py b/clients/python/bindings/superposition_bindings/superposition_client.py index 97fbd967a..f23fb0d8a 100644 --- a/clients/python/bindings/superposition_bindings/superposition_client.py +++ b/clients/python/bindings/superposition_bindings/superposition_client.py @@ -35,6 +35,7 @@ from .superposition_types import Context from .superposition_types import DimensionInfo from .superposition_types import ExperimentStatusType +from .superposition_types import ExtendedMap from .superposition_types import GroupType from .superposition_types import MergeStrategy from .superposition_types import Overrides @@ -47,6 +48,7 @@ from .superposition_types import _UniffiConverterTypeContext from .superposition_types import _UniffiConverterTypeDimensionInfo from .superposition_types import _UniffiConverterTypeExperimentStatusType +from .superposition_types import _UniffiConverterTypeExtendedMap from .superposition_types import _UniffiConverterTypeGroupType from .superposition_types import _UniffiConverterTypeMergeStrategy from .superposition_types import _UniffiConverterTypeOverrides @@ -59,6 +61,7 @@ from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferContext from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferDimensionInfo from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferExperimentStatusType +from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferExtendedMap from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferGroupType from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferMergeStrategy from .superposition_types import _UniffiRustBuffer as _UniffiRustBufferOverrides @@ -495,9 +498,9 @@ def _uniffi_check_contract_api_version(lib): raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") def _uniffi_check_api_checksums(lib): - if lib.uniffi_superposition_core_checksum_func_ffi_eval_config() != 61169: + if lib.uniffi_superposition_core_checksum_func_ffi_eval_config() != 40327: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - if lib.uniffi_superposition_core_checksum_func_ffi_eval_config_with_reasoning() != 47981: + if lib.uniffi_superposition_core_checksum_func_ffi_eval_config_with_reasoning() != 10312: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_superposition_core_checksum_func_ffi_get_applicable_variants() != 58234: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") @@ -610,7 +613,7 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): _UNIFFI_FOREIGN_FUTURE_COMPLETE_VOID = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructVoid, ) _UniffiLib.uniffi_superposition_core_fn_func_ffi_eval_config.argtypes = ( - _UniffiRustBuffer, + _UniffiRustBufferExtendedMap, _UniffiRustBuffer, _UniffiRustBuffer, _UniffiRustBuffer, @@ -622,7 +625,7 @@ class _UniffiForeignFutureStructVoid(ctypes.Structure): ) _UniffiLib.uniffi_superposition_core_fn_func_ffi_eval_config.restype = _UniffiRustBuffer _UniffiLib.uniffi_superposition_core_fn_func_ffi_eval_config_with_reasoning.argtypes = ( - _UniffiRustBuffer, + _UniffiRustBufferExtendedMap, _UniffiRustBuffer, _UniffiRustBuffer, _UniffiRustBuffer, @@ -1552,14 +1555,16 @@ def read(cls, buf): # External type Condition: `from .superposition_types import Condition` +# External type ExtendedMap: `from .superposition_types import ExtendedMap` + # External type Overrides: `from .superposition_types import Overrides` # External type Variants: `from .superposition_types import Variants` # Async support -def ffi_eval_config(default_config: "dict[str, str]",contexts: "typing.List[Context]",overrides: "dict[str, Overrides]",dimensions: "dict[str, DimensionInfo]",query_data: "dict[str, str]",merge_strategy: "MergeStrategy",filter_prefixes: "typing.Optional[typing.List[str]]",experimentation: "typing.Optional[ExperimentationArgs]") -> "dict[str, str]": - _UniffiConverterMapStringString.check_lower(default_config) +def ffi_eval_config(default_config: "ExtendedMap",contexts: "typing.List[Context]",overrides: "dict[str, Overrides]",dimensions: "dict[str, DimensionInfo]",query_data: "dict[str, str]",merge_strategy: "MergeStrategy",filter_prefixes: "typing.Optional[typing.List[str]]",experimentation: "typing.Optional[ExperimentationArgs]") -> "dict[str, str]": + _UniffiConverterTypeExtendedMap.check_lower(default_config) _UniffiConverterSequenceTypeContext.check_lower(contexts) @@ -1576,7 +1581,7 @@ def ffi_eval_config(default_config: "dict[str, str]",contexts: "typing.List[Cont _UniffiConverterOptionalTypeExperimentationArgs.check_lower(experimentation) return _UniffiConverterMapStringString.lift(_uniffi_rust_call_with_error(_UniffiConverterTypeOperationError,_UniffiLib.uniffi_superposition_core_fn_func_ffi_eval_config, - _UniffiConverterMapStringString.lower(default_config), + _UniffiConverterTypeExtendedMap.lower(default_config), _UniffiConverterSequenceTypeContext.lower(contexts), _UniffiConverterMapStringTypeOverrides.lower(overrides), _UniffiConverterMapStringTypeDimensionInfo.lower(dimensions), @@ -1586,8 +1591,8 @@ def ffi_eval_config(default_config: "dict[str, str]",contexts: "typing.List[Cont _UniffiConverterOptionalTypeExperimentationArgs.lower(experimentation))) -def ffi_eval_config_with_reasoning(default_config: "dict[str, str]",contexts: "typing.List[Context]",overrides: "dict[str, Overrides]",dimensions: "dict[str, DimensionInfo]",query_data: "dict[str, str]",merge_strategy: "MergeStrategy",filter_prefixes: "typing.Optional[typing.List[str]]",experimentation: "typing.Optional[ExperimentationArgs]") -> "dict[str, str]": - _UniffiConverterMapStringString.check_lower(default_config) +def ffi_eval_config_with_reasoning(default_config: "ExtendedMap",contexts: "typing.List[Context]",overrides: "dict[str, Overrides]",dimensions: "dict[str, DimensionInfo]",query_data: "dict[str, str]",merge_strategy: "MergeStrategy",filter_prefixes: "typing.Optional[typing.List[str]]",experimentation: "typing.Optional[ExperimentationArgs]") -> "dict[str, str]": + _UniffiConverterTypeExtendedMap.check_lower(default_config) _UniffiConverterSequenceTypeContext.check_lower(contexts) @@ -1604,7 +1609,7 @@ def ffi_eval_config_with_reasoning(default_config: "dict[str, str]",contexts: "t _UniffiConverterOptionalTypeExperimentationArgs.check_lower(experimentation) return _UniffiConverterMapStringString.lift(_uniffi_rust_call_with_error(_UniffiConverterTypeOperationError,_UniffiLib.uniffi_superposition_core_fn_func_ffi_eval_config_with_reasoning, - _UniffiConverterMapStringString.lower(default_config), + _UniffiConverterTypeExtendedMap.lower(default_config), _UniffiConverterSequenceTypeContext.lower(contexts), _UniffiConverterMapStringTypeOverrides.lower(overrides), _UniffiConverterMapStringTypeDimensionInfo.lower(dimensions), diff --git a/crates/cac_client/src/eval.rs b/crates/cac_client/src/eval.rs index 41d89196e..93757e77e 100644 --- a/crates/cac_client/src/eval.rs +++ b/crates/cac_client/src/eval.rs @@ -1,79 +1,60 @@ use std::collections::HashMap; use serde_json::{json, Map, Value}; -use superposition_types::{logic::evaluate_local_cohorts, Config, Overrides}; +use superposition_types::{ + logic::evaluate_local_cohorts, Config, ExtendedMap, Overrides, +}; use crate::{utils::core::MapError, Context, MergeStrategy}; -pub fn merge(doc: &mut Value, patch: &Value) { +pub fn merge(doc: &mut Value, patch: Value) { if !patch.is_object() { - *doc = patch.clone(); + *doc = patch; return; } if !doc.is_object() { *doc = Value::Object(Map::new()); } - let map = doc.as_object_mut().unwrap(); - for (key, value) in patch.as_object().unwrap() { - merge(map.entry(key.as_str()).or_insert(Value::Null), value); - } -} -fn replace_top_level( - doc: &mut Map, - patch: &Value, - mut on_override: impl FnMut(), - override_key: &String, -) { - match patch.as_object() { - Some(patch_map) => { - for (key, value) in patch_map { - doc.insert(key.clone(), value.clone()); - } - on_override(); - } - None => { - log::error!("CAC: found non-object override key: {override_key} in overrides") + if let (Some(map), Value::Object(obj)) = (doc.as_object_mut(), patch) { + for (key, value) in obj { + merge(map.entry(key.as_str()).or_insert(Value::Null), value); } } } -fn get_overrides( +fn get_overrides( query_data: &Map, - contexts: &[Context], - overrides: &HashMap, + contexts: Vec, + mut overrides: HashMap, merge_strategy: &MergeStrategy, - mut on_override_select: Option<&mut dyn FnMut(Context)>, + mut on_override_select: F, ) -> serde_json::Result { let mut required_overrides: Value = json!({}); - let mut on_override_select = |context: Context| { - if let Some(ref mut func) = on_override_select { - func(context) - } - }; for context in contexts { let valid_context = superposition_types::apply(&context.condition, query_data); if valid_context { let override_key = context.override_with_keys.get_key(); - if let Some(overriden_value) = overrides.get(override_key) { + if let Some(overriden_value) = overrides.remove(override_key) { match merge_strategy { - MergeStrategy::REPLACE => replace_top_level( - required_overrides.as_object_mut().unwrap(), - &Value::Object(overriden_value.clone().into()), - || on_override_select(context.clone()), - override_key, - ), + MergeStrategy::REPLACE => { + if let Some(doc) = required_overrides.as_object_mut() { + for (key, value) in overriden_value.into_inner() { + doc.insert(key, value); + } + } + } MergeStrategy::MERGE => { merge( &mut required_overrides, - &Value::Object(overriden_value.clone().into()), + Value::Object(overriden_value.into_inner()), ); - on_override_select(context.clone()) } } + on_override_select(context) } } } @@ -82,77 +63,77 @@ fn get_overrides( } fn merge_overrides_on_default_config( - default_config: &mut Map, + mut default_config: ExtendedMap, overrides: Map, merge_strategy: &MergeStrategy, -) { +) -> ExtendedMap { overrides.into_iter().for_each(|(key, val)| { if let Some(og_val) = default_config.get_mut(&key) { match merge_strategy { MergeStrategy::REPLACE => { - let _ = default_config.insert(key.clone(), val.clone()); + default_config.insert(key, val); } - MergeStrategy::MERGE => merge(og_val, &val), + MergeStrategy::MERGE => merge(og_val, val), } } else { log::error!("CAC: found non-default_config key: {key} in overrides"); } - }) + }); + default_config } pub fn eval_cac( - config: &Config, - query_data: &Map, + config: Config, + query_data: Map, merge_strategy: MergeStrategy, ) -> Result, String> { - let mut default_config = (*config.default_configs).clone(); - let on_override_select: Option<&mut dyn FnMut(Context)> = None; - let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data); + let modified_query_data = evaluate_local_cohorts(config.dimensions, query_data); let overrides: Map = get_overrides( &modified_query_data, - &config.contexts, - &config.overrides, + config.contexts, + config.overrides, &merge_strategy, - on_override_select, + drop, ) .and_then(serde_json::from_value) .map_err_to_string()?; - merge_overrides_on_default_config(&mut default_config, overrides, &merge_strategy); - let overriden_config = default_config; - Ok(overriden_config) + let overriden_config = merge_overrides_on_default_config( + config.default_configs, + overrides, + &merge_strategy, + ); + Ok(overriden_config.into_inner()) } pub fn eval_cac_with_reasoning( - config: &Config, - query_data: &Map, + config: Config, + query_data: Map, merge_strategy: MergeStrategy, ) -> Result, String> { - let mut default_config = (*config.default_configs).clone(); let mut reasoning: Vec = vec![]; - let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data); + let modified_query_data = evaluate_local_cohorts(config.dimensions, query_data); let applied_overrides: Map = get_overrides( &modified_query_data, - &config.contexts, - &config.overrides, + config.contexts, + config.overrides, &merge_strategy, - Some(&mut |context| { + |context| { reasoning.push(json!({ "context": context.condition, "override": context.override_with_keys })) - }), + }, ) .and_then(serde_json::from_value) .map_err_to_string()?; - merge_overrides_on_default_config( - &mut default_config, + let mut overriden_config = merge_overrides_on_default_config( + config.default_configs, applied_overrides, &merge_strategy, ); - let mut overriden_config = default_config; overriden_config.insert("metadata".into(), json!(reasoning)); - Ok(overriden_config) + Ok(overriden_config.into_inner()) } diff --git a/crates/cac_client/src/lib.rs b/crates/cac_client/src/lib.rs index 5848d1f57..717a815fb 100644 --- a/crates/cac_client/src/lib.rs +++ b/crates/cac_client/src/lib.rs @@ -165,13 +165,12 @@ impl Client { config = config.filter_by_prefix(&HashSet::from_iter(prefix_list)); } - let dimension_filtered_config = query_data - .filter(|query_map| !query_map.is_empty()) - .map(|query_map| config.filter_by_dimensions(&query_map)); - - if let Some(filtered_config) = dimension_filtered_config { - config = filtered_config; - }; + match query_data { + Some(query_map) if !query_map.is_empty() => { + config = config.filter_by_dimensions(query_map); + } + _ => (), + } Ok(config) } @@ -204,7 +203,7 @@ impl Client { if let Some(keys) = filter_keys { config = config.filter_by_prefix(&HashSet::from_iter(keys)); } - let evaled_cac = eval::eval_cac(&config, &query_data, merge_strategy)?; + let evaled_cac = eval::eval_cac(config, query_data, merge_strategy)?; self.config_cache.insert(hash_key, evaled_cac.clone()); Ok(evaled_cac) } diff --git a/crates/context_aware_config/src/api/config/handlers.rs b/crates/context_aware_config/src/api/config/handlers.rs index 27a6157ba..5ecd70d6a 100644 --- a/crates/context_aware_config/src/api/config/handlers.rs +++ b/crates/context_aware_config/src/api/config/handlers.rs @@ -619,7 +619,7 @@ async fn get_handler( body.map_or_else(QueryMap::default, |body| body.into_inner().context.into()) }; if !context.is_empty() { - config = config.filter_by_dimensions(&context); + config = config.filter_by_dimensions(context.into_inner()); } let mut response = HttpResponse::Ok(); @@ -693,7 +693,7 @@ async fn resolve_handler( let mut config_version = get_config_version(&query_filters.version, &workspace_context)?; - let mut config = generate_config_from_version( + let config = generate_config_from_version( &mut config_version, &mut conn, &workspace_context.schema_name, @@ -701,7 +701,7 @@ async fn resolve_handler( let (is_smithy, query_data) = setup_query_data(&req, &body, &dimension_params)?; let resolved_config = resolve( - &mut config, + config, query_data, merge_strategy, &mut conn, diff --git a/crates/context_aware_config/src/api/config/helpers.rs b/crates/context_aware_config/src/api/config/helpers.rs index eecc32e5d..9093eeab5 100644 --- a/crates/context_aware_config/src/api/config/helpers.rs +++ b/crates/context_aware_config/src/api/config/helpers.rs @@ -203,7 +203,7 @@ pub fn setup_query_data( } pub fn resolve( - config: &mut Config, + mut config: Config, mut query_data: QueryMap, merge_strategy: Header, conn: &mut DBConnection, @@ -211,7 +211,7 @@ pub fn resolve( workspace_context: &WorkspaceContext, master_encryption_key: &Option, ) -> superposition::Result> { - *config = apply_prefix_filter_to_config(&query_filters.prefix, config.clone())?; + config = apply_prefix_filter_to_config(&query_filters.prefix, config)?; if let Some(context_id) = &query_filters.context_id { config.contexts = if let Some(index) = config @@ -241,12 +241,14 @@ pub fn resolve( let merge_strategy = merge_strategy.into_inner(); let show_reason = query_filters.show_reasoning.unwrap_or_default(); let response = if show_reason { - eval_cac_with_reasoning(config, &query_data, merge_strategy).map_err(|err| { - log::error!("failed to eval cac with err: {}", err); - unexpected_error!("cac eval failed") - }) + eval_cac_with_reasoning(config, query_data.into_inner(), merge_strategy).map_err( + |err| { + log::error!("failed to eval cac with err: {}", err); + unexpected_error!("cac eval failed") + }, + ) } else { - eval_cac(config, &query_data, merge_strategy).map_err(|err| { + eval_cac(config, query_data.into_inner(), merge_strategy).map_err(|err| { log::error!("failed to eval cac with err: {}", err); unexpected_error!("cac eval failed") }) diff --git a/crates/context_aware_config/src/api/context/handlers.rs b/crates/context_aware_config/src/api/context/handlers.rs index 0776c6cf4..9d3e64f75 100644 --- a/crates/context_aware_config/src/api/context/handlers.rs +++ b/crates/context_aware_config/src/api/context/handlers.rs @@ -432,8 +432,10 @@ async fn list_handler( } let dimensions_info = fetch_dimensions_info_map(&mut conn, &workspace_context.schema_name)?; - let dimension_params = - evaluate_local_cohorts_skip_unresolved(&dimensions_info, &dimension_params); + let dimension_params = evaluate_local_cohorts_skip_unresolved( + dimensions_info, + dimension_params.into_inner(), + ); let dimension_keys = dimension_params.keys().cloned().collect::>(); let filter_fn = match filter_params.dimension_match_strategy.unwrap_or_default() { diff --git a/crates/context_aware_config/src/api/context/helpers.rs b/crates/context_aware_config/src/api/context/helpers.rs index 2e726368f..5a6e474ee 100644 --- a/crates/context_aware_config/src/api/context/helpers.rs +++ b/crates/context_aware_config/src/api/context/helpers.rs @@ -438,10 +438,7 @@ pub fn update_override_of_existing_ctx( .select(dsl::override_) .schema_name(schema_name) .first(conn)?; - cac_client::merge( - &mut new_override, - &Value::Object(ctx.override_.clone().into()), - ); + cac_client::merge(&mut new_override, Value::Object(ctx.override_.into_inner())); let new_override_id = hash(&new_override); let new_ctx = Context { override_: Cac::::validate_db_data( diff --git a/crates/experimentation_client/src/interface.rs b/crates/experimentation_client/src/interface.rs index 2d6abcbb7..09a4b3dec 100644 --- a/crates/experimentation_client/src/interface.rs +++ b/crates/experimentation_client/src/interface.rs @@ -205,7 +205,7 @@ pub extern "C" fn expt_get_applicable_variant( Some(prefix_list) }; let variants_result = EXP_RUNTIME.block_on(unsafe { - (*client).get_applicable_variant(&dimensions, &context, &identifier, prefix_list) + (*client).get_applicable_variant(dimensions, context, &identifier, prefix_list) }); variants_result .map(|result| { @@ -252,7 +252,7 @@ pub extern "C" fn expt_get_satisfied_experiments( Some(prefix_list) }; - let context = evaluate_local_cohorts(&dimensions, &context); + let context = evaluate_local_cohorts(dimensions, context); let local = task::LocalSet::new(); local.block_on(&Runtime::new().unwrap(), async move { @@ -309,7 +309,7 @@ pub extern "C" fn expt_get_filtered_satisfied_experiments( Some(prefix_list).filter(|list| !list.is_empty()) }; - let context = evaluate_local_cohorts_skip_unresolved(&dimensions, &context); + let context = evaluate_local_cohorts_skip_unresolved(dimensions, context); let local = task::LocalSet::new(); local.block_on(&Runtime::new().unwrap(), async move { diff --git a/crates/experimentation_client/src/lib.rs b/crates/experimentation_client/src/lib.rs index e67b2de56..0fea2ef55 100644 --- a/crates/experimentation_client/src/lib.rs +++ b/crates/experimentation_client/src/lib.rs @@ -112,8 +112,8 @@ impl Client { pub async fn get_applicable_variant( &self, - dimensions_info: &HashMap, - context: &Map, + dimensions_info: HashMap, + context: Map, identifier: &str, prefix: Option>, ) -> Result, String> { diff --git a/crates/experimentation_platform/src/api/experiments/handlers.rs b/crates/experimentation_platform/src/api/experiments/handlers.rs index 2e0b864e1..47758c931 100644 --- a/crates/experimentation_platform/src/api/experiments/handlers.rs +++ b/crates/experimentation_platform/src/api/experiments/handlers.rs @@ -873,7 +873,7 @@ pub async fn discard( pub async fn get_applicable_variants_helper( db_conn: &mut PooledConnection>, context: Map, - dimensions_info: &HashMap, + dimensions_info: HashMap, identifier: String, workspace_context: &WorkspaceContext, ) -> superposition::Result<(Vec, HashMap)> { @@ -883,7 +883,7 @@ pub async fn get_applicable_variants_helper( .schema_name(&workspace_context.schema_name) .load::(db_conn)?; - let context = evaluate_local_cohorts(dimensions_info, &context); + let context = evaluate_local_cohorts(dimensions_info, context); let buckets = get_applicable_buckets_from_group(&experiment_groups, &context, &identifier); @@ -953,7 +953,7 @@ async fn get_applicable_variants_handler( let (applicable_variants, exps) = get_applicable_variants_helper( &mut conn, context, - &dimensions_info, + dimensions_info, identifier, &workspace_context, ) @@ -1088,8 +1088,8 @@ async fn list_handler( let dimensions_info = fetch_dimensions_info_map(&mut conn, &workspace_context.schema_name)?; let dimension_params = evaluate_local_cohorts_skip_unresolved( - &dimensions_info, - &dimension_params, + dimensions_info, + dimension_params.into_inner(), ); let dimension_keys = dimension_params.keys().cloned().collect::>(); diff --git a/crates/superposition/Cargo.toml b/crates/superposition/Cargo.toml index 9126862b8..c875f946b 100644 --- a/crates/superposition/Cargo.toml +++ b/crates/superposition/Cargo.toml @@ -27,7 +27,6 @@ regex = { workspace = true } reqwest = { workspace = true } rs-snowflake = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } service_utils = { workspace = true } superposition_derives = { workspace = true } superposition_macros = { workspace = true } diff --git a/crates/superposition/src/resolve/handlers.rs b/crates/superposition/src/resolve/handlers.rs index dc54bdc28..b3d1734be 100644 --- a/crates/superposition/src/resolve/handlers.rs +++ b/crates/superposition/src/resolve/handlers.rs @@ -8,7 +8,6 @@ use context_aware_config::api::config::helpers::{ is_not_modified, resolve, setup_query_data, }; use experimentation_platform::api::experiments::handlers::get_applicable_variants_helper; -use serde_json::{Map, Value}; use service_utils::service::types::{AppState, DbConnection, WorkspaceContext}; use superposition_derives::authorized; use superposition_types::{ @@ -59,18 +58,17 @@ async fn resolve_with_exp_handler( // This value is separately needed, as in the following check the value before the modification is required let config_ver = config_version.to_owned(); - let mut config = generate_config_from_version( + let config = generate_config_from_version( &mut config_version, &mut conn, &workspace_context.schema_name, )?; if let (None, Some(identifier)) = (config_ver, identifier_query.identifier) { - let context_map: &Map = &query_data; let (applicable_variants, _) = get_applicable_variants_helper( &mut conn, - context_map.clone(), - &config.dimensions, + query_data.clone().into_inner(), + config.dimensions.clone(), identifier, &workspace_context, ) @@ -79,7 +77,7 @@ async fn resolve_with_exp_handler( } let resolved_config = resolve( - &mut config, + config, query_data, merge_strategy, &mut conn, diff --git a/crates/superposition_core/src/config.rs b/crates/superposition_core/src/config.rs index c8873d82d..a65c9cd4d 100644 --- a/crates/superposition_core/src/config.rs +++ b/crates/superposition_core/src/config.rs @@ -3,24 +3,24 @@ use std::collections::{HashMap, HashSet}; use serde_json::{json, Map, Value}; pub use superposition_types::api::config::MergeStrategy; use superposition_types::{ - logic::evaluate_local_cohorts, Config, Context, DimensionInfo, Overrides, + logic::evaluate_local_cohorts, Config, Context, DimensionInfo, ExtendedMap, Overrides, }; pub fn eval_config( - default_config: Map, - contexts: &[Context], - overrides: &HashMap, - dimensions: &HashMap, - query_data: &Map, + default_configs: ExtendedMap, + contexts: Vec, + overrides: HashMap, + dimensions: HashMap, + query_data: Map, merge_strategy: MergeStrategy, filter_prefixes: Option>, ) -> Result, String> { // Create Config struct to use existing filtering logic let mut config = Config { - default_configs: default_config.into(), - contexts: contexts.to_vec(), - overrides: overrides.clone(), - dimensions: dimensions.clone(), + default_configs, + contexts, + overrides, + dimensions, }; // Apply prefix filtering if keys are provided (using existing superposition_types logic) @@ -31,39 +31,42 @@ pub fn eval_config( } } - let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data); + let modified_query_data = evaluate_local_cohorts(config.dimensions, query_data); let overrides_map: Map = get_overrides( &modified_query_data, - &config.contexts, - &config.overrides, + config.contexts, + config.overrides, &merge_strategy, - None, + drop, )?; // Apply overrides to default config - let mut result_config = config.default_configs; - merge_overrides_on_default_config(&mut result_config, overrides_map, &merge_strategy); + let result_config = merge_overrides_on_default_config( + config.default_configs, + overrides_map, + &merge_strategy, + ); Ok(result_config.into_inner()) } pub fn eval_config_with_reasoning( - default_config: Map, - contexts: &[Context], - overrides: &HashMap, - dimensions: &HashMap, - query_data: &Map, + default_configs: ExtendedMap, + contexts: Vec, + overrides: HashMap, + dimensions: HashMap, + query_data: Map, merge_strategy: MergeStrategy, filter_prefixes: Option>, // Optional prefix filtering ) -> Result, String> { let mut reasoning: Vec = vec![]; let mut config = Config { - default_configs: default_config.into(), - contexts: contexts.to_vec(), - overrides: overrides.clone(), - dimensions: dimensions.clone(), + default_configs, + contexts, + overrides, + dimensions, }; if let Some(prefixes) = filter_prefixes { @@ -73,25 +76,28 @@ pub fn eval_config_with_reasoning( } } - let mut reasoning_collector = |context: Context| { + let reasoning_collector = |context: Context| { reasoning.push(json!({ "context": context.condition, "override": context.override_with_keys })); }; - let modified_query_data = evaluate_local_cohorts(&config.dimensions, query_data); + let modified_query_data = evaluate_local_cohorts(config.dimensions, query_data); let overrides_map = get_overrides( &modified_query_data, - &config.contexts, - &config.overrides, + config.contexts, + config.overrides, &merge_strategy, - Some(&mut reasoning_collector), + reasoning_collector, )?; - let mut result_config = config.default_configs; - merge_overrides_on_default_config(&mut result_config, overrides_map, &merge_strategy); + let mut result_config = merge_overrides_on_default_config( + config.default_configs, + overrides_map, + &merge_strategy, + ); // Add reasoning metadata result_config.insert("metadata".into(), json!(reasoning)); @@ -99,9 +105,9 @@ pub fn eval_config_with_reasoning( Ok(result_config.into_inner()) } -pub fn merge(doc: &mut Value, patch: &Value) { +pub fn merge(doc: &mut Value, patch: Value) { if !patch.is_object() { - *doc = patch.clone(); + *doc = patch; return; } @@ -109,68 +115,44 @@ pub fn merge(doc: &mut Value, patch: &Value) { *doc = Value::Object(Map::new()); } - let map = doc.as_object_mut().unwrap(); - for (key, value) in patch.as_object().unwrap() { - merge(map.entry(key.as_str()).or_insert(Value::Null), value); - } -} - -fn replace_top_level( - doc: &mut Map, - patch: &Value, - mut on_override: impl FnMut(), - override_key: &String, -) { - match patch.as_object() { - Some(patch_map) => { - for (key, value) in patch_map { - doc.insert(key.clone(), value.clone()); - } - on_override(); - } - None => { - log::error!( - "Config: found non-object override key: {override_key} in overrides" - ); + if let (Some(map), Value::Object(obj)) = (doc.as_object_mut(), patch) { + for (key, value) in obj { + merge(map.entry(key.as_str()).or_insert(Value::Null), value); } } } -fn get_overrides( +fn get_overrides( query_data: &Map, - contexts: &[Context], - overrides: &HashMap, + contexts: Vec, + mut overrides: HashMap, merge_strategy: &MergeStrategy, - mut on_override_select: Option<&mut dyn FnMut(Context)>, + mut on_override_select: F, ) -> Result, String> { let mut required_overrides: Value = json!({}); - let mut on_override_select = |context: Context| { - if let Some(ref mut func) = on_override_select { - func(context) - } - }; for context in contexts { let valid_context = superposition_types::apply(&context.condition, query_data); if valid_context { let override_key = context.override_with_keys.get_key(); - if let Some(overriden_value) = overrides.get(override_key) { + if let Some(overriden_value) = overrides.remove(override_key) { match merge_strategy { - MergeStrategy::REPLACE => replace_top_level( - required_overrides.as_object_mut().unwrap(), - &Value::Object(overriden_value.clone().into()), - || on_override_select(context.clone()), - override_key, - ), + MergeStrategy::REPLACE => { + if let Some(doc) = required_overrides.as_object_mut() { + for (key, value) in overriden_value.into_inner() { + doc.insert(key, value); + } + } + } MergeStrategy::MERGE => { merge( &mut required_overrides, - &Value::Object(overriden_value.clone().into()), + Value::Object(overriden_value.into_inner()), ); - on_override_select(context.clone()) } } + on_override_select(context) } } } @@ -182,20 +164,21 @@ fn get_overrides( } fn merge_overrides_on_default_config( - default_config: &mut Map, + mut default_config: ExtendedMap, overrides: Map, merge_strategy: &MergeStrategy, -) { +) -> ExtendedMap { overrides.into_iter().for_each(|(key, val)| { if let Some(og_val) = default_config.get_mut(&key) { match merge_strategy { MergeStrategy::REPLACE => { - let _ = default_config.insert(key.clone(), val.clone()); + default_config.insert(key, val); } - MergeStrategy::MERGE => merge(og_val, &val), + MergeStrategy::MERGE => merge(og_val, val), } } else { log::error!("Config: found non-default_config key: {key} in overrides"); } - }) + }); + default_config } diff --git a/crates/superposition_core/src/experiment.rs b/crates/superposition_core/src/experiment.rs index 974163e37..47fa5c539 100644 --- a/crates/superposition_core/src/experiment.rs +++ b/crates/superposition_core/src/experiment.rs @@ -87,10 +87,10 @@ pub type Experiments = Vec; pub type ExperimentGroups = Vec; pub fn get_applicable_variants( - dimensions_info: &HashMap, + dimensions_info: HashMap, experiments: Experiments, experiment_groups: &ExperimentGroups, - query_data: &Map, + query_data: Map, identifier: &str, prefix: Option>, ) -> Result, String> { diff --git a/crates/superposition_core/src/ffi.rs b/crates/superposition_core/src/ffi.rs index 6c4dc7c6a..ac5585bfe 100644 --- a/crates/superposition_core/src/ffi.rs +++ b/crates/superposition_core/src/ffi.rs @@ -1,6 +1,6 @@ use serde_json::{Map, Value}; use std::collections::HashMap; -use superposition_types::{Config, Context, DimensionInfo, Overrides}; +use superposition_types::{Config, Context, DimensionInfo, ExtendedMap, Overrides}; use thiserror::Error; use crate::{ @@ -27,19 +27,19 @@ fn json_from_map(m: HashMap) -> serde_json::Result, + HashMap, + HashMap, Map, - &[Context], - &HashMap, - &HashMap, - &Map, MergeStrategy, Option>, ) -> Result, String>; #[allow(clippy::too_many_arguments)] fn ffi_eval_logic( - default_config: HashMap, - contexts: &[Context], + default_config: ExtendedMap, + contexts: Vec, overrides: HashMap, dimensions: HashMap, query_data: HashMap, @@ -48,8 +48,6 @@ fn ffi_eval_logic( experimentation: Option, eval_fn: EvalFn, ) -> Result, OperationError> { - let _d = json_from_map(default_config) - .map_err(|err| OperationError::Unexpected(err.to_string()))?; let mut _q = json_from_map(query_data) .map_err(|err| OperationError::Unexpected(err.to_string()))?; @@ -58,10 +56,10 @@ fn ffi_eval_logic( // bucketing procedure. let identifier = e_args.targeting_key; let variants = get_applicable_variants( - &dimensions, + dimensions.clone(), e_args.experiments, &e_args.experiment_groups, - &_q, + _q.clone(), &identifier, filter_prefixes.clone(), ) @@ -70,11 +68,11 @@ fn ffi_eval_logic( } let r = eval_fn( - _d, + default_config, contexts, - &overrides, - &dimensions, - &_q, + overrides, + dimensions, + _q, merge_strategy, filter_prefixes, ) @@ -86,8 +84,8 @@ fn ffi_eval_logic( #[allow(clippy::too_many_arguments)] #[uniffi::export] fn ffi_eval_config( - default_config: HashMap, - contexts: &[Context], + default_config: ExtendedMap, + contexts: Vec, overrides: HashMap, dimensions: HashMap, query_data: HashMap, @@ -111,8 +109,8 @@ fn ffi_eval_config( #[allow(clippy::too_many_arguments)] #[uniffi::export] fn ffi_eval_config_with_reasoning( - default_config: HashMap, - contexts: &[Context], + default_config: ExtendedMap, + contexts: Vec, overrides: HashMap, dimensions: HashMap, query_data: HashMap, @@ -140,15 +138,15 @@ fn ffi_get_applicable_variants( query_data: HashMap, prefix: Option>, ) -> Result, OperationError> { - let _query_data = json_from_map(query_data.clone()) + let _query_data = json_from_map(query_data) .map_err(|err| OperationError::Unexpected(err.to_string()))?; let identifier = eargs.targeting_key; let r = get_applicable_variants( - &dimensions_info, + dimensions_info, eargs.experiments, &eargs.experiment_groups, - &_query_data, + _query_data, &identifier, prefix, ) diff --git a/crates/superposition_core/src/ffi_legacy.rs b/crates/superposition_core/src/ffi_legacy.rs index acd2ed3e4..853fbc7db 100644 --- a/crates/superposition_core/src/ffi_legacy.rs +++ b/crates/superposition_core/src/ffi_legacy.rs @@ -1,10 +1,11 @@ -// src/ffi.rs -use std::collections::HashMap; -use std::ffi::{c_char, CStr, CString}; -use std::ptr; +use std::{ + collections::HashMap, + ffi::{c_char, CStr, CString}, + ptr, +}; use serde_json::{Map, Value}; -use superposition_types::{Context, DimensionInfo, Overrides}; +use superposition_types::{Context, DimensionInfo, ExtendedMap, Overrides}; use crate::config::{self, MergeStrategy}; use crate::experiment::{ExperimentGroups, ExperimentationArgs}; @@ -70,7 +71,7 @@ pub unsafe extern "C" fn core_get_resolved_config( // Parse all parameters let default_config = match parse_json::>(default_config_json) { - Ok(config) => config, + Ok(config) => ExtendedMap::from(config), Err(e) => { copy_string(ebuf, format!("Failed to parse default_config: {}", e)); return ptr::null_mut(); @@ -148,10 +149,10 @@ pub unsafe extern "C" fn core_get_resolved_config( let identifier = e_args.targeting_key; match get_applicable_variants( - &dimensions, + dimensions.clone(), e_args.experiments, &e_args.experiment_groups, - &query_data, + query_data.clone(), &identifier, filter_prefixes.clone(), ) { @@ -168,10 +169,10 @@ pub unsafe extern "C" fn core_get_resolved_config( // Call pure config resolution logic match config::eval_config( default_config, - &contexts, - &overrides, - &dimensions, - &query_data, + contexts, + overrides, + dimensions, + query_data, merge_strategy, filter_prefixes, ) { @@ -219,7 +220,7 @@ pub unsafe extern "C" fn core_get_resolved_config_with_reasoning( // Parse parameters (same logic as above) let default_config = match parse_json::>(default_config_json) { - Ok(config) => config, + Ok(config) => ExtendedMap::from(config), Err(e) => { copy_string(ebuf, format!("Failed to parse default_config: {}", e)); return ptr::null_mut(); @@ -298,10 +299,10 @@ pub unsafe extern "C" fn core_get_resolved_config_with_reasoning( let identifier = e_args.targeting_key; match get_applicable_variants( - &dimensions, + dimensions.clone(), e_args.experiments, &e_args.experiment_groups, - &query_data, + query_data.clone(), &identifier, filter_prefixes.clone(), ) { @@ -318,10 +319,10 @@ pub unsafe extern "C" fn core_get_resolved_config_with_reasoning( // Call config resolution with reasoning match config::eval_config_with_reasoning( default_config, - &contexts, - &overrides, - &dimensions, - &query_data, + contexts, + overrides, + dimensions, + query_data, merge_strategy, filter_prefixes, ) { @@ -432,10 +433,10 @@ pub unsafe extern "C" fn core_get_applicable_variants( // Call the experimentation logic match get_applicable_variants( - &dimensions, + dimensions, experiments, &experiment_groups, - &query_data, + query_data, &identifier, filter_prefixes, ) { diff --git a/crates/superposition_core/src/toml/test.rs b/crates/superposition_core/src/toml/test.rs index 9ab50a5fd..3905f12f9 100644 --- a/crates/superposition_core/src/toml/test.rs +++ b/crates/superposition_core/src/toml/test.rs @@ -644,13 +644,12 @@ max_count = 95 let mut dims = Map::new(); dims.insert("os".to_string(), Value::String("linux".to_string())); - let default_configs = (*config.default_configs).clone(); let result = crate::eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims, + config.default_configs, + config.contexts, + config.overrides, + config.dimensions, + dims, crate::MergeStrategy::MERGE, None, ) diff --git a/crates/superposition_core/tests/test_filter_debug.rs b/crates/superposition_core/tests/test_filter_debug.rs index a0b8dfcd5..b4fae082b 100644 --- a/crates/superposition_core/tests/test_filter_debug.rs +++ b/crates/superposition_core/tests/test_filter_debug.rs @@ -79,7 +79,7 @@ timeout = 90 // Simulate what API does - filter by empty dimension data let empty_dimensions: Map = Map::new(); - let filtered_config = config.filter_by_dimensions(&empty_dimensions); + let filtered_config = config.filter_by_dimensions(empty_dimensions); println!("\n=== After filter (empty dimensions) ==="); println!("Contexts count: {}", filtered_config.contexts.len()); diff --git a/crates/superposition_provider/src/client.rs b/crates/superposition_provider/src/client.rs index f4f1273c1..4e84e6fb5 100644 --- a/crates/superposition_provider/src/client.rs +++ b/crates/superposition_provider/src/client.rs @@ -6,7 +6,7 @@ pub use open_feature::{ provider::{ProviderMetadata, ProviderStatus, ResolutionDetails}, EvaluationContext, }; -use serde_json::Value; +use serde_json::{Map, Value}; use superposition_core::experiment::ExperimentGroups; use superposition_core::{ eval_config, get_applicable_variants, Experiments, MergeStrategy, @@ -24,7 +24,7 @@ use crate::utils::ConversionUtils; pub struct CacConfig { superposition_options: SuperpositionOptions, options: ConfigurationOptions, - fallback_config: Option>, + fallback_config: Option>, cached_config: Arc>>, last_updated: Arc>>>, polling_task_cancellation_token: Arc>>, @@ -225,18 +225,18 @@ impl CacConfig { /// Evaluate configuration for given context and return resolved values pub async fn evaluate_config( &self, - query_data: &serde_json::Map, + query_data: Map, prefix_filter: Option<&[String]>, - ) -> Result> { + ) -> Result> { let cached_config = self.cached_config.read().await; - match cached_config.as_ref() { + match cached_config.to_owned() { Some(cached_config) => { // Use ConversionUtils to evaluate config eval_config( - (*cached_config.default_configs).clone(), - &cached_config.contexts, - &cached_config.overrides, - &cached_config.dimensions, + cached_config.default_configs, + cached_config.contexts, + cached_config.overrides, + cached_config.dimensions, query_data, MergeStrategy::MERGE, prefix_filter.map(|p| p.to_vec()), @@ -583,8 +583,8 @@ impl ExperimentationConfig { pub async fn get_applicable_variants( &self, - dimensions_info: &HashMap, - contexts: &serde_json::Map, + dimensions_info: HashMap, + contexts: Map, identifier: Option, ) -> Result> { let cached_experiments = self.cached_experiments.read().await; diff --git a/crates/superposition_provider/src/provider.rs b/crates/superposition_provider/src/provider.rs index 7fe4be3c0..d9c474674 100644 --- a/crates/superposition_provider/src/provider.rs +++ b/crates/superposition_provider/src/provider.rs @@ -143,7 +143,7 @@ impl SuperpositionProvider { let dimensions_info = self.get_dimensions_info().await; let variant_ids = if let Some(exp_config) = &self.exp_config { exp_config - .get_applicable_variants(&dimensions_info, &context, targeting_key) + .get_applicable_variants(dimensions_info, context.clone(), targeting_key) .await? } else { vec![] @@ -155,7 +155,7 @@ impl SuperpositionProvider { ); match &self.cac_config { - Some(cac_config) => cac_config.evaluate_config(&context, None).await, + Some(cac_config) => cac_config.evaluate_config(context, None).await, None => Err(SuperpositionError::ConfigError( "CAC config not initialized".into(), )), @@ -186,7 +186,7 @@ impl SuperpositionProvider { if let Some(dimension_filter) = dimension_filter.filter(|query_map| !query_map.is_empty()) { - cached_config = cached_config.filter_by_dimensions(&dimension_filter); + cached_config = cached_config.filter_by_dimensions(dimension_filter); }; Ok(cached_config) diff --git a/crates/superposition_provider/src/utils.rs b/crates/superposition_provider/src/utils.rs index 9f5d400d4..734d59532 100644 --- a/crates/superposition_provider/src/utils.rs +++ b/crates/superposition_provider/src/utils.rs @@ -621,8 +621,8 @@ impl ConversionUtils { /// Evaluate config using superposition_types logic and return resolved values pub fn evaluate_config( - config: &Config, - dimension_data: &Map, + config: Config, + dimension_data: Map, prefix_filter: Option<&[String]>, ) -> Result> { debug!( @@ -651,16 +651,17 @@ impl ConversionUtils { final_config.contexts.len() ); - // Start with default configs - let mut result = final_config.default_configs.into_inner(); - - // Apply overrides based on context priority (higher priority wins) - let mut sorted_contexts = final_config.contexts.clone(); - sorted_contexts.sort_by_key(|c| std::cmp::Reverse(c.priority)); // Sort by priority descending + let Config { + default_configs: mut result, + contexts: mut sorted_contexts, + overrides, + .. + } = final_config; + sorted_contexts.sort_by_key(|c| std::cmp::Reverse(c.weight)); for context in sorted_contexts { if let Some(override_key) = context.override_with_keys.first() { - if let Some(overrides) = final_config.overrides.get(override_key) { + if let Some(overrides) = overrides.get(override_key) { let override_map: Map = overrides.clone().into(); for (override_key, value) in override_map { result.insert(override_key, value); @@ -676,7 +677,8 @@ impl ConversionUtils { ); // Convert Map to HashMap - let final_result: HashMap = result.into_iter().collect(); + let final_result: HashMap = + result.into_inner().into_iter().collect(); Ok(final_result) } diff --git a/crates/superposition_types/src/config.rs b/crates/superposition_types/src/config.rs index 151910636..2005c0034 100644 --- a/crates/superposition_types/src/config.rs +++ b/crates/superposition_types/src/config.rs @@ -88,6 +88,10 @@ impl Overrides { } Ok(Self(override_map)) } + + pub fn into_inner(self) -> Map { + self.0 + } } impl IntoIterator for Overrides { @@ -289,28 +293,30 @@ pub struct Config { } impl Config { - pub fn filter_by_dimensions(&self, dimension_data: &Map) -> Self { - let modified_context = - evaluate_local_cohorts_skip_unresolved(&self.dimensions, dimension_data); + pub fn filter_by_dimensions(self, dimension_data: Map) -> Self { + let modified_context = evaluate_local_cohorts_skip_unresolved( + self.dimensions.clone(), + dimension_data, + ); - let filtered_context = - Context::filter_by_eval(self.contexts.clone(), &modified_context); + let filtered_context = Context::filter_by_eval(self.contexts, &modified_context); + let mut overrides = self.overrides; let filtered_overrides: HashMap = filtered_context .iter() .flat_map(|ele| { let override_with_key = ele.override_with_keys.get_key(); - self.overrides - .get(override_with_key) - .map(|value| (override_with_key.to_string(), value.clone())) + overrides + .remove(override_with_key) + .map(|value| (override_with_key.to_string(), value)) }) .collect(); Self { contexts: filtered_context, overrides: filtered_overrides, - default_configs: self.default_configs.clone(), - dimensions: self.dimensions.clone(), + default_configs: self.default_configs, + dimensions: self.dimensions, } } diff --git a/crates/superposition_types/src/config/tests.rs b/crates/superposition_types/src/config/tests.rs index f5f3031e5..962a49dee 100644 --- a/crates/superposition_types/src/config/tests.rs +++ b/crates/superposition_types/src/config/tests.rs @@ -91,17 +91,17 @@ fn filter_by_dimensions_with_dimension() { let config = with_dimensions::get_config(); assert_eq!( - config.filter_by_dimensions(&get_dimension_data1()), + config.clone().filter_by_dimensions(get_dimension_data1()), with_dimensions::get_dimension_filtered_config1() ); assert_eq!( - config.filter_by_dimensions(&get_dimension_data2()), + config.clone().filter_by_dimensions(get_dimension_data2()), with_dimensions::get_dimension_filtered_config2() ); assert_eq!( - config.filter_by_dimensions(&get_dimension_data3()), + config.filter_by_dimensions(get_dimension_data3()), get_dimension_filtered_config3_with_dimension() ); } @@ -111,17 +111,17 @@ fn filter_by_dimensions_without_dimension() { let config = without_dimensions::get_config(); assert_eq!( - config.filter_by_dimensions(&get_dimension_data1()), + config.clone().filter_by_dimensions(get_dimension_data1()), without_dimensions::get_dimension_filtered_config1() ); assert_eq!( - config.filter_by_dimensions(&get_dimension_data2()), + config.clone().filter_by_dimensions(get_dimension_data2()), without_dimensions::get_dimension_filtered_config2() ); assert_eq!( - config.filter_by_dimensions(&get_dimension_data3()), + config.filter_by_dimensions(get_dimension_data3()), get_dimension_filtered_config3_without_dimension() ); } diff --git a/crates/superposition_types/src/custom_query.rs b/crates/superposition_types/src/custom_query.rs index a5933a0f7..77d450c39 100644 --- a/crates/superposition_types/src/custom_query.rs +++ b/crates/superposition_types/src/custom_query.rs @@ -186,6 +186,12 @@ where #[serde(from = "HashMap")] pub struct QueryMap(Map); +impl QueryMap { + pub fn into_inner(self) -> Map { + self.0 + } +} + impl IsEmpty for QueryMap { fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/crates/superposition_types/src/logic.rs b/crates/superposition_types/src/logic.rs index aa4f116b2..100642967 100644 --- a/crates/superposition_types/src/logic.rs +++ b/crates/superposition_types/src/logic.rs @@ -77,8 +77,7 @@ pub fn partial_apply( } fn _evaluate_local_cohort_dimension( - cohort_based_on: &str, - cohort_based_on_value: &Value, + evaluation_data: Value, schema: &Map, ) -> Option { let definitions_object = schema.get("definitions")?.as_object()?; @@ -95,7 +94,6 @@ fn _evaluate_local_cohort_dimension( for cohort_option in cohort_enums { let jsonlogic = definitions_object.get(cohort_option)?; // Find the first matching cohort definition - let evaluation_data = serde_json::json!({cohort_based_on: cohort_based_on_value}); if jsonlogic::apply(jsonlogic, &evaluation_data) == Ok(Value::Bool(true)) { return Some(cohort_option.to_string()); } @@ -105,11 +103,13 @@ fn _evaluate_local_cohort_dimension( } fn evaluate_local_cohort_dimension( - cohort_based_on: &str, - cohort_based_on_value: &Value, + cohort_based_on: String, + cohort_based_on_value: Value, schema: &Map, ) -> String { - _evaluate_local_cohort_dimension(cohort_based_on, cohort_based_on_value, schema) + let evaluation_data = + Value::Object(Map::from_iter([(cohort_based_on, cohort_based_on_value)])); + _evaluate_local_cohort_dimension(evaluation_data, schema) .unwrap_or_else(|| "otherwise".to_string()) } @@ -133,27 +133,23 @@ fn evaluate_local_cohorts_dependency( // Depth-first traversal of dependencies while let Some((cohort_dimension, based_on, based_on_val)) = stack.pop() { if let Some(dimension_info) = dimensions.get(&cohort_dimension) { - let mut cohort_val = None; match &dimension_info.dimension_type { DimensionType::LocalCohort(_) => { let cohort_value = Value::String(evaluate_local_cohort_dimension( - &based_on, - &based_on_val, + based_on, + based_on_val, &dimension_info.schema, )); - modified_context - .insert(cohort_dimension.clone(), cohort_value.clone()); - cohort_val = Some(cohort_value); + modified_context.insert(cohort_dimension.clone(), cohort_value); } _ => { if let Some(value) = query_data.get(&cohort_dimension) { modified_context.insert(cohort_dimension.clone(), value.clone()); - cohort_val = Some(value.clone()); } } } - if let Some(cohort_val) = cohort_val { + if let Some(cohort_val) = modified_context.get(&cohort_dimension) { stack.extend( dimension_info .dependency_graph @@ -170,28 +166,28 @@ fn evaluate_local_cohorts_dependency( } fn _evaluate_local_cohorts( - dimensions: &HashMap, - query_data: &Map, + dimensions: HashMap, + mut query_data: Map, skip_unresolved: bool, ) -> Map { if dimensions.is_empty() { - return query_data.clone(); + return query_data; } let mut modified_context = Map::new(); // Start from dimensions that are closest to root in each tree - for dimension_key in dimensions_to_start_from(dimensions, query_data) { - if let Some(value) = query_data.get(&dimension_key) { + for dimension_key in dimensions_to_start_from(&dimensions, &query_data) { + if let Some(value) = query_data.remove(&dimension_key) { if let Some(dimension_info) = dimensions.get(&dimension_key) { modified_context.insert(dimension_key.to_string(), value.clone()); evaluate_local_cohorts_dependency( &dimension_key, - value, + &value, &dimension_info.dependency_graph, - dimensions, + &dimensions, &mut modified_context, - query_data, + &query_data, ); } } @@ -226,16 +222,16 @@ fn _evaluate_local_cohorts( /// Returned value, might have a different value for local cohort dimensions based on its based on dimensions, /// if the value provided for the local cohort was incorrect in the query data. pub fn evaluate_local_cohorts( - dimensions: &HashMap, - query_data: &Map, + dimensions: HashMap, + query_data: Map, ) -> Map { _evaluate_local_cohorts(dimensions, query_data, false) } /// Same as evaluate_local_cohorts but does not set unresolved local cohorts to "otherwise" pub fn evaluate_local_cohorts_skip_unresolved( - dimensions: &HashMap, - query_data: &Map, + dimensions: HashMap, + query_data: Map, ) -> Map { _evaluate_local_cohorts(dimensions, query_data, true) } @@ -256,10 +252,9 @@ pub fn dimensions_to_start_from( .collect::>(); for root_dimension in regular_dimensions { - let dependency_graph = &dimensions + let dependency_graph = dimensions .get(&root_dimension) - .map(|data| data.dependency_graph.clone()) - .unwrap_or_default(); + .map(|data| &data.dependency_graph); let mut stack = vec![root_dimension]; @@ -278,7 +273,7 @@ pub fn dimensions_to_start_from( stack.extend( dependency_graph - .get(¤t_dimension) + .and_then(|dg| dg.get(¤t_dimension)) .cloned() .unwrap_or_default(), ); diff --git a/crates/superposition_types/src/logic/tests.rs b/crates/superposition_types/src/logic/tests.rs index 88bedc66f..d163a6692 100644 --- a/crates/superposition_types/src/logic/tests.rs +++ b/crates/superposition_types/src/logic/tests.rs @@ -190,7 +190,7 @@ fn test_evaluate_local_cohorts_empty_dimensions() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data.clone()); assert_eq!(result, query_data); } @@ -219,7 +219,7 @@ fn test_evaluate_local_cohorts_with_regular_dimension() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("regular_dim"), Some(&json!("value1"))); } @@ -253,7 +253,7 @@ fn test_evaluate_local_cohorts_with_local_cohort_otherwise() { let query_data = Map::new(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("local_cohort_dim"), Some(&json!("otherwise"))); } @@ -301,7 +301,7 @@ fn test_evaluate_local_cohorts_with_dependency() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("test"), Some(&json!("trigger_value"))); assert_eq!(result.get("testdep"), Some(&json!("cohort1"))); } @@ -322,13 +322,19 @@ fn test_evaluate_local_cohort_dimension_function() { .unwrap() .clone(); - let result = evaluate_local_cohort_dimension("test_value", &json!(42), &schema); + let result = + evaluate_local_cohort_dimension("test_value".to_string(), json!(42), &schema); assert_eq!(result, "exactly_42"); - let result = evaluate_local_cohort_dimension("test_value", &json!(100), &schema); + let result = + evaluate_local_cohort_dimension("test_value".to_string(), json!(100), &schema); assert_eq!(result, "otherwise"); - let result = evaluate_local_cohort_dimension("test_value", &json!("string"), &schema); + let result = evaluate_local_cohort_dimension( + "test_value".to_string(), + json!("string"), + &schema, + ); assert_eq!(result, "otherwise"); } @@ -400,7 +406,7 @@ fn test_sample_dependency_graph() { assert_eq!(start_dims.len(), 1); assert!(start_dims.contains(&"test".to_string())); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions.clone(), query_data); assert_eq!(result.get("test"), Some(&json!("main_value"))); assert_eq!(result.get("test_remote"), Some(&json!("remote_value_a"))); assert_eq!(result.get("testdep"), Some(&json!("group_a"))); @@ -418,7 +424,7 @@ fn test_sample_dependency_graph() { assert!(start_dims2.contains(&"testtest".to_string())); assert!(start_dims2.contains(&"testdep".to_string())); - let result2 = evaluate_local_cohorts(&dimensions, &query_data2); + let result2 = evaluate_local_cohorts(dimensions, query_data2); assert_eq!(result2.get("testtest"), Some(&json!("leaf_value"))); assert_eq!(result2.get("testdep"), Some(&json!("otherwise"))); } @@ -648,7 +654,7 @@ fn test_evaluate_local_cohorts_missing_schema_definitions() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("root"), Some(&json!("some_value"))); assert_eq!(result.get("local_cohort"), Some(&json!("otherwise"))); } @@ -694,7 +700,7 @@ fn test_evaluate_local_cohorts_invalid_json_logic() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("root"), Some(&json!("trigger_value"))); assert_eq!(result.get("local_cohort"), Some(&json!("otherwise"))); } @@ -764,7 +770,7 @@ fn test_evaluate_local_cohorts_complex_json_logic() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions.clone(), query_data); assert_eq!(result.get("user"), Some(&json!(user_value))); assert_eq!(result.get("cohort"), Some(&json!(expected_cohort))); } @@ -825,7 +831,7 @@ fn test_evaluate_local_cohorts_remote_cohort_passthrough() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("root"), Some(&json!("root_value"))); assert_eq!(result.get("remote"), Some(&json!("remote_value_a"))); assert_eq!(result.get("local"), Some(&json!("group_a"))); @@ -863,7 +869,7 @@ fn test_evaluate_local_cohorts_fills_missing_local_cohorts() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("orphan_cohort"), Some(&json!("otherwise"))); } @@ -926,7 +932,11 @@ fn test_dimensions_to_start_from_with_multiple_regular_roots() { fn test_evaluate_local_cohort_dimension_edge_cases() { // Test with empty schema let empty_schema = Map::new(); - let result = evaluate_local_cohort_dimension("test", &json!("value"), &empty_schema); + let result = evaluate_local_cohort_dimension( + "test".to_string(), + json!("value"), + &empty_schema, + ); assert_eq!(result, "otherwise"); // Test with schema missing enum @@ -941,8 +951,11 @@ fn test_evaluate_local_cohort_dimension_edge_cases() { .unwrap() .clone(); - let result = - evaluate_local_cohort_dimension("test", &json!("value"), &schema_no_enum); + let result = evaluate_local_cohort_dimension( + "test".to_string(), + json!("value"), + &schema_no_enum, + ); assert_eq!(result, "otherwise"); // Test with schema missing definitions @@ -953,8 +966,11 @@ fn test_evaluate_local_cohort_dimension_edge_cases() { .unwrap() .clone(); - let result = - evaluate_local_cohort_dimension("test", &json!("value"), &schema_no_definitions); + let result = evaluate_local_cohort_dimension( + "test".to_string(), + json!("value"), + &schema_no_definitions, + ); assert_eq!(result, "otherwise"); // Test with malformed enum (not array) @@ -970,8 +986,11 @@ fn test_evaluate_local_cohort_dimension_edge_cases() { .unwrap() .clone(); - let result = - evaluate_local_cohort_dimension("test", &json!("value"), &schema_bad_enum); + let result = evaluate_local_cohort_dimension( + "test".to_string(), + json!("value"), + &schema_bad_enum, + ); assert_eq!(result, "otherwise"); } @@ -1046,7 +1065,7 @@ fn test_local_cohort_based_on_local_cohort() { .unwrap() .clone(); - let result1 = evaluate_local_cohorts(&dimensions, &query_data1); + let result1 = evaluate_local_cohorts(dimensions.clone(), query_data1); assert_eq!(result1.get("user_tier"), Some(&json!(1500))); assert_eq!(result1.get("user_category"), Some(&json!("premium"))); assert_eq!(result1.get("user_segment"), Some(&json!("vip"))); @@ -1059,7 +1078,7 @@ fn test_local_cohort_based_on_local_cohort() { .unwrap() .clone(); - let result2 = evaluate_local_cohorts(&dimensions, &query_data2); + let result2 = evaluate_local_cohorts(dimensions.clone(), query_data2); assert_eq!(result2.get("user_tier"), Some(&json!(500))); assert_eq!(result2.get("user_category"), Some(&json!("standard"))); assert_eq!(result2.get("user_segment"), Some(&json!("regular"))); @@ -1072,7 +1091,7 @@ fn test_local_cohort_based_on_local_cohort() { .unwrap() .clone(); - let result3 = evaluate_local_cohorts(&dimensions, &query_data3); + let result3 = evaluate_local_cohorts(dimensions.clone(), query_data3); assert_eq!(result3.get("user_tier"), Some(&json!(50))); assert_eq!(result3.get("user_category"), Some(&json!("basic"))); assert_eq!(result3.get("user_segment"), Some(&json!("regular"))); @@ -1085,7 +1104,7 @@ fn test_local_cohort_based_on_local_cohort() { .unwrap() .clone(); - let result4 = evaluate_local_cohorts(&dimensions, &query_data4); + let result4 = evaluate_local_cohorts(dimensions, query_data4); assert_eq!(result4.get("user_tier"), Some(&json!(999))); assert_eq!(result4.get("user_category"), Some(&json!("otherwise"))); assert_eq!(result4.get("user_segment"), Some(&json!("new"))); @@ -1166,7 +1185,7 @@ fn test_local_cohort_chain_with_intermediate_query_data() { assert_eq!(start_dims.len(), 1); assert!(start_dims.contains(&"country_group".to_string())); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions, query_data); assert_eq!(result.get("country_group"), Some(&json!("asia"))); assert_eq!(result.get("market_segment"), Some(&json!("emerging"))); // region should not be evaluated since it's not in the starting path @@ -1267,7 +1286,7 @@ fn test_local_cohort_complex_branching() { .unwrap() .clone(); - let result = evaluate_local_cohorts(&dimensions, &query_data); + let result = evaluate_local_cohorts(dimensions.clone(), query_data); assert_eq!(result.get("device_type"), Some(&json!("android"))); assert_eq!(result.get("device_category"), Some(&json!("mobile"))); assert_eq!(result.get("ui_theme"), Some(&json!("dark"))); @@ -1281,7 +1300,7 @@ fn test_local_cohort_complex_branching() { .unwrap() .clone(); - let result2 = evaluate_local_cohorts(&dimensions, &query_data2); + let result2 = evaluate_local_cohorts(dimensions, query_data2); assert_eq!(result2.get("device_type"), Some(&json!("windows"))); assert_eq!(result2.get("device_category"), Some(&json!("desktop"))); assert_eq!(result2.get("ui_theme"), Some(&json!("light"))); @@ -1462,7 +1481,7 @@ fn test_multi_tree_complex_branching() { assert!(start_dims1.contains(&"location".to_string())); assert!(start_dims1.contains(&"user_tier".to_string())); - let result1 = evaluate_local_cohorts(&dimensions, &query_data1); + let result1 = evaluate_local_cohorts(dimensions.clone(), query_data1); assert_eq!(result1.get("user_preferences"), Some(&json!("detailed"))); assert_eq!(result1.get("location"), Some(&json!("usa"))); assert_eq!(result1.get("region"), Some(&json!("west"))); @@ -1489,7 +1508,7 @@ fn test_multi_tree_complex_branching() { assert!(start_dims2.contains(&"timezone".to_string())); assert!(start_dims2.contains(&"region".to_string())); - let result2 = evaluate_local_cohorts(&dimensions, &query_data2); + let result2 = evaluate_local_cohorts(dimensions.clone(), query_data2); assert_eq!(result2.get("user_id"), Some(&json!(1500))); assert_eq!(result2.get("user_tier"), Some(&json!("premium"))); assert_eq!(result2.get("user_preferences"), Some(&json!("detailed"))); @@ -1729,7 +1748,7 @@ fn test_regular_overrides_local_cohort_in_query() { assert_eq!(start_dims1.len(), 1); assert!(start_dims1.contains(&"user_score".to_string())); - let result1 = evaluate_local_cohorts(&dimensions, &query_data1); + let result1 = evaluate_local_cohorts(dimensions.clone(), query_data1); assert_eq!(result1.get("user_score"), Some(&json!(950))); assert_eq!(result1.get("user_category"), Some(&json!("gold"))); assert_eq!(result1.get("reward_tier"), Some(&json!("premium"))); @@ -1747,7 +1766,7 @@ fn test_regular_overrides_local_cohort_in_query() { assert_eq!(start_dims2.len(), 1); assert!(start_dims2.contains(&"user_category".to_string())); - let result2 = evaluate_local_cohorts(&dimensions, &query_data2); + let result2 = evaluate_local_cohorts(dimensions.clone(), query_data2); assert_eq!(result2.get("user_category"), Some(&json!("silver"))); assert_eq!(result2.get("reward_tier"), Some(&json!("standard"))); @@ -1763,7 +1782,7 @@ fn test_regular_overrides_local_cohort_in_query() { assert_eq!(start_dims3.len(), 1); assert!(start_dims3.contains(&"user_category".to_string())); - let result3 = evaluate_local_cohorts(&dimensions, &query_data3); + let result3 = evaluate_local_cohorts(dimensions.clone(), query_data3); assert_eq!(result3.get("user_category"), Some(&json!("otherwise"))); assert_eq!(result3.get("reward_tier"), Some(&json!("otherwise"))); @@ -1777,7 +1796,7 @@ fn test_regular_overrides_local_cohort_in_query() { .unwrap() .clone(); - let result4 = evaluate_local_cohorts(&dimensions, &query_data4); + let result4 = evaluate_local_cohorts(dimensions, query_data4); assert_eq!(result4.get("user_score"), Some(&json!(300))); assert_eq!(result4.get("user_category"), Some(&json!("bronze"))); assert_eq!(result4.get("reward_tier"), Some(&json!("basic"))); diff --git a/examples/experimentation_client_integration_example/src/main.rs b/examples/experimentation_client_integration_example/src/main.rs index c5417357d..69f61bc02 100644 --- a/examples/experimentation_client_integration_example/src/main.rs +++ b/examples/experimentation_client_integration_example/src/main.rs @@ -46,8 +46,8 @@ async fn get_variants( }); let variant = state .get_applicable_variant( - &HashMap::new(), - &contexts.as_object().cloned().unwrap_or_default(), + HashMap::new(), + contexts.as_object().cloned().unwrap_or_default(), &identifier, None, ) diff --git a/examples/superposition_toml_example/src/main.rs b/examples/superposition_toml_example/src/main.rs index 548f5fcc5..ce08eb21b 100644 --- a/examples/superposition_toml_example/src/main.rs +++ b/examples/superposition_toml_example/src/main.rs @@ -43,16 +43,13 @@ fn main() -> Result<(), Box> { "vehicle_type".to_string(), Value::String("bike".to_string()), ); - - // Clone default configs once for all evaluations - let default_configs = (*config.default_configs).clone(); - + let config1 = config.clone(); // Clone the config for each eval to avoid ownership issues let result1 = eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims1, + config1.default_configs, + config1.contexts, + config1.overrides, + config1.dimensions, + dims1, MergeStrategy::MERGE, None, )?; @@ -74,12 +71,13 @@ fn main() -> Result<(), Box> { dims2.insert("city".to_string(), Value::String("Bangalore".to_string())); dims2.insert("vehicle_type".to_string(), Value::String("cab".to_string())); + let config2 = config.clone(); // Clone the config for each eval to avoid ownership issues let result2 = eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims2, + config2.default_configs, + config2.contexts, + config2.overrides, + config2.dimensions, + dims2, MergeStrategy::MERGE, None, )?; @@ -102,12 +100,13 @@ fn main() -> Result<(), Box> { dims3.insert("vehicle_type".to_string(), Value::String("cab".to_string())); dims3.insert("hour_of_day".to_string(), Value::Number(6.into())); + let config3 = config.clone(); // Clone the config for each eval to avoid ownership issues let result3 = eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims3, + config3.default_configs, + config3.contexts, + config3.overrides, + config3.dimensions, + dims3, MergeStrategy::MERGE, None, )?; @@ -131,12 +130,13 @@ fn main() -> Result<(), Box> { Value::String("auto".to_string()), ); + let config4 = config.clone(); // Clone the config for each eval to avoid ownership issues let result4 = eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims4, + config4.default_configs, + config4.contexts, + config4.overrides, + config4.dimensions, + dims4, MergeStrategy::MERGE, None, )?; @@ -158,11 +158,11 @@ fn main() -> Result<(), Box> { dims5.insert("city".to_string(), Value::String("Chennai".to_string())); let result5 = eval_config( - default_configs.clone(), - &config.contexts, - &config.overrides, - &config.dimensions, - &dims5, + config.default_configs, + config.contexts, + config.overrides, + config.dimensions, + dims5, MergeStrategy::MERGE, None, )?;