diff --git a/Cargo.lock b/Cargo.lock index 54aac3c457..c16397213c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1425,6 +1425,7 @@ dependencies = [ "faststr", "log", "md5", + "pyo3", "regex", "serde", "serde-bool", @@ -3004,6 +3005,15 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "inout" version = "0.1.4" @@ -3994,6 +4004,68 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dc55d7dec32ecaf61e0bd90b3d2392d721a28b95cfd23c3e176eccefbeab2f2" +[[package]] +name = "pyo3" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.87", +] + [[package]] name = "quote" version = "1.0.37" @@ -4954,6 +5026,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + [[package]] name = "target-triple" version = "0.1.4" @@ -5589,6 +5667,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/datadog-ffe/Cargo.toml b/datadog-ffe/Cargo.toml index dad975e63f..c0c32c61ff 100644 --- a/datadog-ffe/Cargo.toml +++ b/datadog-ffe/Cargo.toml @@ -20,8 +20,9 @@ md5 = { version = "0.7.0", default-features = false } regex = "1.10.4" serde-bool = { version = "0.1.3", default-features = false } serde_with = { version = "3.11.0", default-features = false, features = ["base64", "hex", "macros"] } -thiserror = "2.0.3" -url = "2.5.0" +thiserror = { version = "2.0.3", default-features = false } +url = { version = "2.5.0", default-features = false, features = ["std"] } +pyo3 = { version = "0.25", optional = true, default-features = false, features = ["macros"] } [dev-dependencies] env_logger = "0.10" @@ -31,3 +32,6 @@ criterion = { version = "0.5", features = ["html_reports"] } name = "ffe-eval" harness = false path = "benches/eval.rs" + +[features] +pyo3 = ["dep:pyo3"] diff --git a/datadog-ffe/build.rs b/datadog-ffe/build.rs deleted file mode 100644 index c12528b339..0000000000 --- a/datadog-ffe/build.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ -// SPDX-License-Identifier: Apache-2.0 - -use std::fs; -use std::io::Write; -use std::path::Path; - -fn main() { - // Tell Cargo to rerun this build script if the test data directory changes - println!("cargo:rerun-if-changed=tests/data/tests"); - - let test_dir = Path::new("tests/data/tests"); - let out_dir = std::env::var("OUT_DIR").unwrap(); - let dest_path = Path::new(&out_dir).join("sdk_tests.rs"); - - let mut file = fs::File::create(dest_path).unwrap(); - - // Read all JSON files in the test directory - let mut test_files = Vec::new(); - if let Ok(entries) = fs::read_dir(test_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.extension().and_then(|s| s.to_str()) == Some("json") { - if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { - test_files.push(stem.to_string()); - } - } - } - } - - // Sort for consistent output - test_files.sort(); - - // Generate the entire test code - writeln!( - file, - "// This file is automatically generated by build.rs\n" - ) - .unwrap(); - - // Helper function - writeln!(file, "// Helper function to run a single test file").unwrap(); - writeln!(file, "#[allow(dead_code)]").unwrap(); - writeln!( - file, - "fn run_test_file(config: &Configuration, test_file_path: &str, now: chrono::DateTime) {{" - ) - .unwrap(); - writeln!(file, " let f = File::open(test_file_path)").unwrap(); - writeln!( - file, - " .unwrap_or_else(|e| panic!(\"Failed to open test file {{}}: {{}}\", test_file_path, e));" - ) - .unwrap(); - writeln!( - file, - " let test_cases: Vec = serde_json::from_reader(f)" - ) - .unwrap(); - writeln!( - file, - " .unwrap_or_else(|e| panic!(\"Failed to parse test file {{}}: {{}}\", test_file_path, e));" - ) - .unwrap(); - writeln!(file).unwrap(); - writeln!(file, " for test_case in test_cases {{").unwrap(); - writeln!(file, " let default_assignment =").unwrap(); - writeln!( - file, - " AssignmentValue::from_wire(test_case.variation_type, test_case.default_value)" - ) - .unwrap(); - writeln!(file, " .unwrap();").unwrap(); - writeln!(file).unwrap(); - writeln!( - file, - " let targeting_key = test_case.targeting_key.clone();" - ) - .unwrap(); - writeln!( - file, - " let subject = EvaluationContext::new(test_case.targeting_key, test_case.attributes);" - ) - .unwrap(); - writeln!(file, " let result = get_assignment(").unwrap(); - writeln!(file, " Some(config),").unwrap(); - writeln!(file, " &test_case.flag,").unwrap(); - writeln!(file, " &subject,").unwrap(); - writeln!(file, " Some(test_case.variation_type),").unwrap(); - writeln!(file, " now,").unwrap(); - writeln!(file, " )").unwrap(); - writeln!(file, " .unwrap_or(None);").unwrap(); - writeln!(file).unwrap(); - writeln!(file, " let result_assignment = result").unwrap(); - writeln!(file, " .as_ref()").unwrap(); - writeln!(file, " .map(|assignment| &assignment.value)").unwrap(); - writeln!(file, " .unwrap_or(&default_assignment);").unwrap(); - writeln!(file, " let expected_assignment =").unwrap(); - writeln!( - file, - " AssignmentValue::from_wire(test_case.variation_type, test_case.result.value)" - ) - .unwrap(); - writeln!(file, " .unwrap();").unwrap(); - writeln!(file).unwrap(); - writeln!(file, " assert_eq!(").unwrap(); - writeln!(file, " result_assignment, &expected_assignment,").unwrap(); - writeln!( - file, - " \"Test case failed for subject {{:?}} in file {{}}\"," - ) - .unwrap(); - writeln!(file, " targeting_key, test_file_path").unwrap(); - writeln!(file, " );").unwrap(); - writeln!(file, " }}").unwrap(); - writeln!(file, "}}").unwrap(); - writeln!(file).unwrap(); - - // Generate individual test functions - for test_file in &test_files { - let test_name = format!("evaluation_sdk_{}", test_file.replace('-', "_")); - - writeln!(file, "#[test]").unwrap(); - writeln!( - file, - "#[cfg_attr(miri, ignore)] // these tests are way too slow on miri" - ) - .unwrap(); - writeln!(file, "fn {}() {{", test_name).unwrap(); - writeln!( - file, - " let _ = env_logger::builder().is_test(true).try_init();" - ) - .unwrap(); - writeln!(file).unwrap(); - writeln!(file, " let config = UniversalFlagConfig::from_json(").unwrap(); - writeln!( - file, - " std::fs::read(\"tests/data/flags-v1.json\").unwrap()" - ) - .unwrap(); - writeln!(file, " )").unwrap(); - writeln!(file, " .unwrap();").unwrap(); - writeln!( - file, - " let config = Configuration::from_server_response(config);" - ) - .unwrap(); - writeln!(file, " let now = Utc::now();").unwrap(); - writeln!(file).unwrap(); - writeln!( - file, - " let test_file_path = \"tests/data/tests/{}.json\";", - test_file - ) - .unwrap(); - writeln!(file, " run_test_file(&config, test_file_path, now);").unwrap(); - writeln!(file, "}}").unwrap(); - writeln!(file).unwrap(); - } -} diff --git a/datadog-ffe/src/rules_based/attributes.rs b/datadog-ffe/src/rules_based/attributes.rs index 5581d58d1e..e2b99f534f 100644 --- a/datadog-ffe/src/rules_based/attributes.rs +++ b/datadog-ffe/src/rules_based/attributes.rs @@ -10,10 +10,10 @@ use crate::rules_based::Str; /// Attribute for evaluation context. See `From` implementations for initialization. #[derive(Debug, Clone, PartialEq, PartialOrd, derive_more::From, Serialize, Deserialize)] #[from(f64, bool, Str, String, &str, Arc, Arc, Cow<'_, str>)] -pub struct Attribute(AttributeValueImpl); +pub struct Attribute(AttributeImpl); #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize, derive_more::From)] #[serde(untagged)] -enum AttributeValueImpl { +enum AttributeImpl { #[from] Number(f64), #[from(forward)] @@ -26,7 +26,7 @@ enum AttributeValueImpl { impl Attribute { pub(crate) fn is_null(&self) -> bool { - self == &Attribute(AttributeValueImpl::Null) + self == &Attribute(AttributeImpl::Null) } /// Try coercing attribute to a number. @@ -35,8 +35,8 @@ impl Attribute { /// number. pub(crate) fn coerce_to_number(&self) -> Option { match &self.0 { - AttributeValueImpl::Number(v) => Some(*v), - AttributeValueImpl::String(s) => s.parse().ok(), + AttributeImpl::Number(v) => Some(*v), + AttributeImpl::String(s) => s.parse().ok(), _ => None, } } @@ -46,19 +46,61 @@ impl Attribute { /// String attributes are returned as is. Number and boolean attributes are converted to string. pub(crate) fn coerce_to_string(&self) -> Option> { match &self.0 { - AttributeValueImpl::String(s) => Some(Cow::Borrowed(s)), - AttributeValueImpl::Number(v) => Some(Cow::Owned(v.to_string())), - AttributeValueImpl::Boolean(v) => { - Some(Cow::Borrowed(if *v { "true" } else { "false" })) - } - AttributeValueImpl::Null => None, + AttributeImpl::String(s) => Some(Cow::Borrowed(s)), + AttributeImpl::Number(v) => Some(Cow::Owned(v.to_string())), + AttributeImpl::Boolean(v) => Some(Cow::Borrowed(if *v { "true" } else { "false" })), + AttributeImpl::Null => None, } } pub(crate) fn as_str(&self) -> Option<&Str> { match self { - Attribute(AttributeValueImpl::String(s)) => Some(s), + Attribute(AttributeImpl::String(s)) => Some(s), _ => None, } } } + +#[cfg(feature = "pyo3")] +mod pyo3_impl { + use super::*; + + use pyo3::{ + exceptions::PyTypeError, + prelude::*, + types::{PyBool, PyFloat, PyInt, PyString}, + }; + + /// Convert Python value to Attribute. + /// + /// The following types are currently supported: + /// - `str` + /// - `int` + /// - `float` + /// - `bool` + /// - `NoneType` + /// + /// Note that nesting is not currently supported and will throw an error. + impl<'py> FromPyObject<'py> for Attribute { + #[inline] + fn extract_bound(value: &Bound<'py, PyAny>) -> PyResult { + if let Ok(s) = value.downcast::() { + return Ok(Attribute(AttributeImpl::String(s.to_cow()?.into()))); + } + // In Python, Bool inherits from Int, so it must be checked first here. + if let Ok(s) = value.downcast::() { + return Ok(Attribute(AttributeImpl::Boolean(s.is_true()))); + } + if let Ok(s) = value.downcast::() { + return Ok(Attribute(AttributeImpl::Number(s.value()))); + } + if let Ok(s) = value.downcast::() { + return Ok(Attribute(AttributeImpl::Number(s.extract::()?))); + } + if value.is_none() { + return Ok(Attribute(AttributeImpl::Null)); + } + Err(PyTypeError::new_err("invalid type for attribute")) + } + } +} diff --git a/datadog-ffe/src/rules_based/error.rs b/datadog-ffe/src/rules_based/error.rs index 53ed1cd80c..bb95b5484f 100644 --- a/datadog-ffe/src/rules_based/error.rs +++ b/datadog-ffe/src/rules_based/error.rs @@ -1,59 +1,20 @@ // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; - use serde::{Deserialize, Serialize}; use crate::rules_based::ufc::VariationType; -/// Represents a result type for operations in the feature flagging SDK. -/// -/// This type alias is used throughout the SDK to indicate the result of operations that may return -/// errors specific to the feature flagging SDK. +/// Enum representing all possible reasons that could result in evaluation returning an error or +/// default assignment. /// -/// This `Result` type is a standard Rust `Result` type where the error variant is defined by the -/// ffe-specific [`Error`] enum. -pub type Result = std::result::Result; - -/// Enum representing possible errors that can occur in the feature flagging SDK. -#[derive(thiserror::Error, Debug, Clone)] -#[non_exhaustive] -pub enum Error { - /// Error evaluating a flag. - #[error(transparent)] - EvaluationError(EvaluationError), - - /// Invalid base URL configuration. - #[error("invalid base_url configuration")] - InvalidBaseUrl(#[source] url::ParseError), - - /// The request was unauthorized, possibly due to an invalid API key. - #[error("unauthorized, api_key is likely invalid")] - Unauthorized, - - /// Indicates that the poller thread panicked. This should normally never happen. - #[error("poller thread panicked")] - PollerThreadPanicked, - - /// An I/O error. - #[error(transparent)] - // std::io::Error is not clonable, so we're wrapping it in an Arc. - Io(Arc), -} - -impl From for Error { - fn from(value: std::io::Error) -> Self { - Self::Io(Arc::new(value)) - } -} - -/// Enum representing possible errors that can occur during evaluation. +/// Not all of these are technically "errors"—some can be expected to occur frequently (e.g., +/// `FlagDisabled` or `DefaultAllocation`). #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[non_exhaustive] pub enum EvaluationError { - /// Requested flag has invalid type. + /// Requested flag has unexpected type. #[error("invalid flag type (expected: {expected:?}, found: {found:?})")] TypeMismatch { /// Expected type of the flag. @@ -62,23 +23,13 @@ pub enum EvaluationError { found: VariationType, }, - /// Configuration received from the server is invalid for the SDK. This should normally never - /// happen and is likely a signal that you should update SDK. - #[error("unexpected configuration received from the server")] - UnexpectedConfigurationError, -} - -/// Enum representing all possible reasons that could result in evaluation returning an error or -/// default assignment. -#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub(crate) enum EvaluationFailure { - /// True evaluation error that should be returned to the user. - #[error(transparent)] - Error(EvaluationError), + /// Failed to parse configuration. This should normally never happen and is likely a signal + /// that you should update SDK. + #[error("failed to parse configuration")] + ConfigurationParseError, /// Configuration has not been fetched yet. - #[error("configuration has not been fetched yet")] + #[error("flags configuration is missing")] ConfigurationMissing, /// The requested flag configuration was not found. It either does not exist or is disabled. @@ -93,16 +44,4 @@ pub(crate) enum EvaluationFailure { /// being assigned. #[error("default allocation is matched and is serving NULL")] DefaultAllocationNull, - - #[error("flag resolved to a non-bandit variation")] - NonBanditVariation, - - #[error("no actions were supplied to bandit evaluation")] - NoActionsSuppliedForBandit, -} - -impl From for EvaluationFailure { - fn from(value: EvaluationError) -> EvaluationFailure { - EvaluationFailure::Error(value) - } } diff --git a/datadog-ffe/src/rules_based/eval/eval_assignment.rs b/datadog-ffe/src/rules_based/eval/eval_assignment.rs index 65523054ee..5f157f5efd 100644 --- a/datadog-ffe/src/rules_based/eval/eval_assignment.rs +++ b/datadog-ffe/src/rules_based/eval/eval_assignment.rs @@ -4,12 +4,12 @@ use chrono::{DateTime, Utc}; use crate::rules_based::{ - error::{EvaluationError, EvaluationFailure}, + error::EvaluationError, ufc::{ Allocation, Assignment, AssignmentReason, CompiledFlagsConfig, Flag, Shard, Split, - Timestamp, VariationType, + VariationType, }, - Configuration, EvaluationContext, + Configuration, EvaluationContext, Timestamp, }; /// Evaluate the specified feature flag for the given subject and return assigned variation and @@ -20,13 +20,13 @@ pub fn get_assignment( subject: &EvaluationContext, expected_type: Option, now: DateTime, -) -> Result, EvaluationError> { +) -> Result { let Some(config) = configuration else { log::trace!( flag_key, targeting_key = subject.targeting_key(); - "returning default assignment because of: {}", EvaluationFailure::ConfigurationMissing); - return Ok(None); + "returning default assignment because of: {}", EvaluationError::ConfigurationMissing); + return Err(EvaluationError::ConfigurationMissing); }; config.eval_flag(flag_key, subject, expected_type, now) @@ -39,49 +39,30 @@ impl Configuration { context: &EvaluationContext, expected_type: Option, now: DateTime, - ) -> Result, EvaluationError> { + ) -> Result { let result = self .flags .compiled .eval_flag(flag_key, context, expected_type, now); - match result { + match &result { Ok(assignment) => { log::trace!( - flag_key, - targeting_key = context.targeting_key(), - assignment:serde = assignment.value; - "evaluated a flag"); - Ok(Some(assignment)) - } - - Err(EvaluationFailure::ConfigurationMissing) => { - log::warn!( - flag_key, - targeting_key = context.targeting_key(); - "evaluating a flag before flags configuration has been fetched"); - Ok(None) - } - - Err(EvaluationFailure::Error(err)) => { - log::warn!( flag_key, - targeting_key = context.targeting_key(); - "error occurred while evaluating a flag: {err}", - ); - Err(err) + targeting_key = context.targeting_key(), + assignment:serde = assignment.value; + "evaluated a flag"); } - // Non-Error failures are considered normal conditions and usually don't need extra - // attention, so we remap them to Ok(None) before returning to the user. Err(err) => { log::trace!( flag_key, targeting_key = context.targeting_key(); "returning default assignment because of: {err}"); - Ok(None) } } + + result } } @@ -93,7 +74,7 @@ impl CompiledFlagsConfig { subject: &EvaluationContext, expected_type: Option, now: DateTime, - ) -> Result { + ) -> Result { let flag = self.get_flag(flag_key)?; if let Some(ty) = expected_type { @@ -103,24 +84,24 @@ impl CompiledFlagsConfig { flag.eval(subject, now) } - fn get_flag(&self, flag_key: &str) -> Result<&Flag, EvaluationFailure> { + fn get_flag(&self, flag_key: &str) -> Result<&Flag, EvaluationError> { self.flags .get(flag_key) - .ok_or(EvaluationFailure::FlagUnrecognizedOrDisabled)? + .ok_or(EvaluationError::FlagUnrecognizedOrDisabled)? .as_ref() .map_err(Clone::clone) } } impl Flag { - fn verify_type(&self, ty: VariationType) -> Result<(), EvaluationFailure> { + fn verify_type(&self, ty: VariationType) -> Result<(), EvaluationError> { if self.variation_type == ty { Ok(()) } else { - Err(EvaluationFailure::Error(EvaluationError::TypeMismatch { + Err(EvaluationError::TypeMismatch { expected: ty, found: self.variation_type, - })) + }) } } @@ -128,14 +109,14 @@ impl Flag { &self, subject: &EvaluationContext, now: DateTime, - ) -> Result { + ) -> Result { let Some((allocation, (split, reason))) = self.allocations.iter().find_map(|allocation| { let result = allocation.get_matching_split(subject, now); result .ok() .map(|(split, reason)| (allocation, (split, reason))) }) else { - return Err(EvaluationFailure::DefaultAllocationNull); + return Err(EvaluationError::DefaultAllocationNull); }; let value = split.value.clone(); @@ -220,7 +201,11 @@ impl Shard { #[cfg(test)] mod tests { - use std::{collections::HashMap, fs::File, sync::Arc}; + use std::{ + collections::HashMap, + fs::{self, File}, + sync::Arc, + }; use chrono::Utc; use serde::{Deserialize, Serialize}; @@ -247,8 +232,50 @@ mod tests { value: serde_json::Value, } - // Include the SDK tests generated by build.rs at compile time - // The build script automatically discovers all test files in tests/data/tests/ - // and generates a separate test function for each one - include!(concat!(env!("OUT_DIR"), "/sdk_tests.rs")); + #[test] + #[cfg_attr(miri, ignore)] // this test is way too slow on miri + fn evaluation_sdk_test_data() { + let _ = env_logger::builder().is_test(true).try_init(); + + let config = + UniversalFlagConfig::from_json(std::fs::read("tests/data/flags-v1.json").unwrap()) + .unwrap(); + let config = Configuration::from_server_response(config); + let now = Utc::now(); + + for entry in fs::read_dir("tests/data/tests/").unwrap() { + let entry = entry.unwrap(); + println!("Processing test file: {:?}", entry.path()); + + let f = File::open(entry.path()).unwrap(); + let test_cases: Vec = serde_json::from_reader(f).unwrap(); + + for test_case in test_cases { + let default_assignment = + AssignmentValue::from_wire(test_case.variation_type, test_case.default_value) + .unwrap(); + + print!("test subject {:?} ... ", test_case.targeting_key); + let subject = EvaluationContext::new(test_case.targeting_key, test_case.attributes); + let result = get_assignment( + Some(&config), + &test_case.flag, + &subject, + Some(test_case.variation_type), + now, + ); + + let result_assingment = result + .as_ref() + .map(|assignment| &assignment.value) + .unwrap_or(&default_assignment); + let expected_assignment = + AssignmentValue::from_wire(test_case.variation_type, test_case.result.value) + .unwrap(); + + assert_eq!(result_assingment, &expected_assignment); + println!("ok"); + } + } + } } diff --git a/datadog-ffe/src/rules_based/eval/evaluation_context.rs b/datadog-ffe/src/rules_based/eval/evaluation_context.rs index 5e8b7bd83e..d9e3be63f1 100644 --- a/datadog-ffe/src/rules_based/eval/evaluation_context.rs +++ b/datadog-ffe/src/rules_based/eval/evaluation_context.rs @@ -49,3 +49,60 @@ impl EvaluationContext { None } } + +#[cfg(feature = "pyo3")] +mod pyo3_impl { + use super::*; + + use pyo3::{intern, prelude::*, types::PyDict}; + + /// Accepts either a dict with `"targeting_key"` and `"attributes"` items, or any object with + /// `targeting_key` and `attributes` attributes. + /// + /// # Examples + /// + /// ```python + /// {"targeting_key": "user1", "attributes": {"attr1": 42}} + /// ``` + /// + /// ```python + /// @dataclass + /// class EvaluationContext: + /// targeting_key: Optional[str] + /// attributes: dict[str, Any] + /// + /// EvaluationContext(targeting_key="user1", attributes={"attr1": 42}) + /// ``` + impl<'py> FromPyObject<'py> for EvaluationContext { + #[inline] + fn extract_bound(value: &Bound<'py, PyAny>) -> PyResult { + let py = value.py(); + + let (targeting_key, attributes) = if let Ok(dict) = value.downcast::() { + ( + dict.get_item(intern!(py, "targeting_key"))?, + dict.get_item(intern!(py, "attributes"))?, + ) + } else { + ( + value.getattr_opt(intern!(py, "targeting_key"))?, + value.getattr_opt(intern!(py, "attributes"))?, + ) + }; + + let context = EvaluationContext { + targeting_key: targeting_key + .map(|it| it.extract()) + .transpose()? + .unwrap_or_else(|| Str::from_static_str("").into()), + attributes: attributes + .map(|it| it.extract()) + .transpose()? + .map(Arc::new) + .unwrap_or_default(), + }; + + Ok(context) + } + } +} diff --git a/datadog-ffe/src/rules_based/mod.rs b/datadog-ffe/src/rules_based/mod.rs index 1c2747f6a7..f3197c36d2 100644 --- a/datadog-ffe/src/rules_based/mod.rs +++ b/datadog-ffe/src/rules_based/mod.rs @@ -12,7 +12,7 @@ mod ufc; pub use attributes::Attribute; pub use configuration::Configuration; -pub use error::{Error, EvaluationError, Result}; +pub use error::EvaluationError; pub use eval::{get_assignment, EvaluationContext}; pub use str::Str; pub use timestamp::{now, Timestamp}; diff --git a/datadog-ffe/src/rules_based/str.rs b/datadog-ffe/src/rules_based/str.rs index ae9a1b6404..6afb96569c 100644 --- a/datadog-ffe/src/rules_based/str.rs +++ b/datadog-ffe/src/rules_based/str.rs @@ -111,3 +111,31 @@ impl log::kv::ToValue for Str { log::kv::Value::from_display(self) } } + +#[cfg(feature = "pyo3")] +mod pyo3_impl { + use std::convert::Infallible; + + use super::*; + + use pyo3::{prelude::*, types::PyString}; + + impl<'py> FromPyObject<'py> for Str { + #[inline] + fn extract_bound(value: &Bound<'py, PyAny>) -> PyResult { + let s = value.downcast::()?; + Ok(Str::from(s.to_cow()?)) + } + } + + impl<'py> IntoPyObject<'py> for &Str { + type Target = PyString; + type Output = Bound<'py, PyString>; + type Error = Infallible; + + #[inline] + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PyString::new(py, self.as_str())) + } + } +} diff --git a/datadog-ffe/src/rules_based/ufc/assignment.rs b/datadog-ffe/src/rules_based/ufc/assignment.rs index 18cefb96a3..d60ab534c7 100644 --- a/datadog-ffe/src/rules_based/ufc/assignment.rs +++ b/datadog-ffe/src/rules_based/ufc/assignment.rs @@ -11,7 +11,7 @@ use crate::rules_based::Str; use super::VariationType; /// Reason for assignment evaluation result. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum AssignmentReason { /// Assignment was made based on targeting rules or time bounds. @@ -299,3 +299,73 @@ impl AssignmentValue { } } } + +#[cfg(feature = "pyo3")] +mod pyo3_impl { + + use super::*; + + use pyo3::{ + exceptions::PyValueError, + prelude::*, + types::{PyDict, PyList}, + }; + + impl<'py> IntoPyObject<'py> for &AssignmentValue { + type Target = PyAny; + type Output = Bound<'py, PyAny>; + type Error = PyErr; + + #[inline] + fn into_pyobject(self, py: Python<'py>) -> Result { + let obj = match self { + AssignmentValue::String(v) => v.into_pyobject(py)?.into_any(), + AssignmentValue::Integer(v) => v.into_pyobject(py)?.into_any(), + AssignmentValue::Float(v) => v.into_pyobject(py)?.into_any(), + AssignmentValue::Boolean(v) => v.into_pyobject(py)?.to_owned().into_any(), + AssignmentValue::Json(v) => json_to_pyobject(v, py)?, + }; + Ok(obj) + } + } + + fn json_to_pyobject<'py>( + value: &serde_json::Value, + py: Python<'py>, + ) -> Result, PyErr> { + use serde_json::Value; + let v = match value { + Value::Null => py.None().into_bound(py), + Value::Bool(v) => v.into_pyobject(py)?.to_owned().into_any(), + Value::Number(number) => { + if let Some(v) = number.as_u128() { + v.into_pyobject(py)?.into_any() + } else if let Some(v) = number.as_i128() { + v.into_pyobject(py)?.into_any() + } else if let Some(v) = number.as_f64() { + v.into_pyobject(py)?.into_any() + } else { + // NOTE: this can only happen if serde_json is compiled with arbitrary-precision + // and it failed to parse the number / the number is larger than f64::MAX. + return Err(PyValueError::new_err("unable to convert number to python")); + } + } + Value::String(s) => s.into_pyobject(py)?.into_any(), + Value::Array(values) => { + let vals = values + .iter() + .map(|it| json_to_pyobject(it, py)) + .collect::, _>>()?; + PyList::new(py, vals)?.into_any() + } + Value::Object(map) => { + let dict = PyDict::new(py); + for (key, value) in map { + dict.set_item(key, json_to_pyobject(value, py)?)?; + } + dict.into_any() + } + }; + Ok(v) + } +} diff --git a/datadog-ffe/src/rules_based/ufc/compiled_flag_config.rs b/datadog-ffe/src/rules_based/ufc/compiled_flag_config.rs index 93b906b79f..4d71abf8c0 100644 --- a/datadog-ffe/src/rules_based/ufc/compiled_flag_config.rs +++ b/datadog-ffe/src/rules_based/ufc/compiled_flag_config.rs @@ -5,13 +5,11 @@ use std::{collections::HashMap, sync::Arc}; use serde::Deserialize; -use crate::rules_based::{ - error::EvaluationFailure, sharder::PreSaltedSharder, Error, EvaluationError, Str, -}; +use crate::rules_based::{error::EvaluationError, sharder::PreSaltedSharder, Str, Timestamp}; use super::{ AllocationWire, AssignmentValue, Environment, FlagWire, RuleWire, ShardRange, ShardWire, - SplitWire, Timestamp, UniversalFlagConfigWire, VariationType, + SplitWire, UniversalFlagConfigWire, VariationType, }; #[derive(Debug)] @@ -25,14 +23,13 @@ pub struct UniversalFlagConfig { #[serde(from = "UniversalFlagConfigWire")] pub(crate) struct CompiledFlagsConfig { /// When configuration was last updated. - #[allow(dead_code)] pub created_at: Timestamp, /// Environment this configuration belongs to. pub environment: Environment, /// Flags configuration. /// /// For flags that failed to parse or are disabled, we store the evaluation failure directly. - pub flags: HashMap>, + pub flags: HashMap>, } #[derive(Debug)] @@ -66,11 +63,8 @@ pub(crate) struct Shard { } impl UniversalFlagConfig { - pub fn from_json(json: Vec) -> Result { - let config: CompiledFlagsConfig = serde_json::from_slice(&json).map_err(|err| { - log::warn!("failed to compile flag configuration: {err:?}"); - Error::EvaluationError(EvaluationError::UnexpectedConfigurationError) - })?; + pub fn from_json(json: Vec) -> Result { + let config: CompiledFlagsConfig = serde_json::from_slice(&json)?; Ok(UniversalFlagConfig { wire_json: json, compiled: config, @@ -85,33 +79,29 @@ impl UniversalFlagConfig { impl From for CompiledFlagsConfig { fn from(config: UniversalFlagConfigWire) -> Self { let flags = config - .data - .attributes .flags .into_iter() .map(|(key, flag)| { ( key, Option::from(flag) - .ok_or(EvaluationFailure::Error( - EvaluationError::UnexpectedConfigurationError, - )) + .ok_or(EvaluationError::ConfigurationParseError) .and_then(compile_flag), ) }) .collect(); CompiledFlagsConfig { - created_at: config.data.attributes.created_at.into(), - environment: config.data.attributes.environment, + created_at: config.created_at, + environment: config.environment, flags, } } } -fn compile_flag(flag: FlagWire) -> Result { +fn compile_flag(flag: FlagWire) -> Result { if !flag.enabled { - return Err(EvaluationFailure::FlagDisabled); + return Err(EvaluationError::FlagDisabled); } let variation_values = flag @@ -119,7 +109,7 @@ fn compile_flag(flag: FlagWire) -> Result { .into_values() .map(|variation| { let assignment_value = AssignmentValue::from_wire(flag.variation_type, variation.value) - .ok_or(EvaluationError::UnexpectedConfigurationError)?; + .ok_or(EvaluationError::ConfigurationParseError)?; Ok((variation.key, assignment_value)) }) @@ -175,7 +165,7 @@ fn compile_split( let result = variation_values .get(&split.variation_key) .cloned() - .ok_or(EvaluationError::UnexpectedConfigurationError)?; + .ok_or(EvaluationError::ConfigurationParseError)?; Ok(Split { shards, diff --git a/datadog-ffe/src/rules_based/ufc/models.rs b/datadog-ffe/src/rules_based/ufc/models.rs index 8ff9f5b2d6..2188abce3e 100644 --- a/datadog-ffe/src/rules_based/ufc/models.rs +++ b/datadog-ffe/src/rules_based/ufc/models.rs @@ -6,57 +6,14 @@ use std::{collections::HashMap, sync::Arc}; use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::rules_based::{Error, EvaluationError, Str}; - -#[allow(missing_docs)] -pub type Timestamp = crate::rules_based::timestamp::Timestamp; - -// Temporary workaround till we figure out one proper format -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum WireTimestamp { - Iso8601(Timestamp), - UnixMs(i64), -} - -impl From for Timestamp { - fn from(value: WireTimestamp) -> Self { - match value { - WireTimestamp::Iso8601(ts) => ts, - WireTimestamp::UnixMs(unix) => { - Timestamp::from_timestamp_millis(unix).expect("timestamp should be in range") - } - } - } -} - -/// JSON API wrapper for Universal Flag Configuration. -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct UniversalFlagConfigWire { - /// JSON API data envelope. - pub data: UniversalFlagConfigData, -} - -/// JSON API data structure for Universal Flag Configuration. -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct UniversalFlagConfigData { - /// JSON API type field. - #[serde(rename = "type")] - pub data_type: String, - /// JSON API id field. - pub id: String, - /// JSON API attributes containing the actual UFC data. - pub attributes: UniversalFlagConfigAttributes, -} +use crate::rules_based::{EvaluationError, Str, Timestamp}; /// Universal Flag Configuration attributes. This contains the actual flag configuration data. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] -pub(crate) struct UniversalFlagConfigAttributes { +pub(crate) struct UniversalFlagConfigWire { /// When configuration was last updated. - pub created_at: WireTimestamp, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub format: Option, + pub created_at: Timestamp, /// Environment this configuration belongs to. pub environment: Environment, /// Flags configuration. @@ -66,14 +23,6 @@ pub(crate) struct UniversalFlagConfigAttributes { pub flags: HashMap>, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum ConfigurationFormat { - Client, - Server, - Precomputed, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Environment { @@ -281,7 +230,7 @@ impl From for Option { } impl TryFrom for Condition { - type Error = Error; + type Error = EvaluationError; fn try_from(condition: ConditionWire) -> Result { let attribute = condition.attribute; @@ -296,9 +245,7 @@ impl TryFrom for Condition { "failed to parse condition: {:?} condition with non-string condition value", condition.operator ); - return Err(Error::EvaluationError( - EvaluationError::UnexpectedConfigurationError, - )); + return Err(EvaluationError::ConfigurationParseError); } }; let regex = match Regex::new(®ex_string) { @@ -307,9 +254,7 @@ impl TryFrom for Condition { log::warn!( "failed to parse condition: failed to compile regex {regex_string:?}: {err:?}" ); - return Err(Error::EvaluationError( - EvaluationError::UnexpectedConfigurationError, - )); + return Err(EvaluationError::ConfigurationParseError); } }; @@ -337,9 +282,7 @@ impl TryFrom for Condition { "failed to parse condition: comparison value is not a number: {:?}", condition.value ); - return Err(Error::EvaluationError( - EvaluationError::UnexpectedConfigurationError, - )); + return Err(EvaluationError::ConfigurationParseError); }; ConditionCheck::Comparison { operator, @@ -355,9 +298,7 @@ impl TryFrom for Condition { "failed to parse condition: membership condition with non-array value: {:?}", condition.value ); - return Err(Error::EvaluationError( - EvaluationError::UnexpectedConfigurationError, - )); + return Err(EvaluationError::ConfigurationParseError); } }; ConditionCheck::Membership { @@ -371,9 +312,7 @@ impl TryFrom for Condition { log::warn!( "failed to parse condition: IS_NULL condition with non-boolean condition value" ); - return Err(Error::EvaluationError( - EvaluationError::UnexpectedConfigurationError, - )); + return Err(EvaluationError::ConfigurationParseError); }; ConditionCheck::Null { expected_null } } @@ -510,14 +449,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] // this test is way too slow on miri fn parse_flags_v1() { - let json_content = { - let path = if std::path::Path::new("tests/data/flags-v1.json").exists() { - "tests/data/flags-v1.json" - } else { - "domains/ffe/libs/flagging/rust/evaluation/tests/data/flags-v1.json" - }; - std::fs::read_to_string(path).unwrap() - }; + let json_content = std::fs::read_to_string("tests/data/flags-v1.json").unwrap(); let _ufc: UniversalFlagConfigWire = serde_json::from_str(&json_content).unwrap(); } @@ -526,29 +458,26 @@ mod tests { let ufc: UniversalFlagConfigWire = serde_json::from_str( r#" { - "data": { - "type": "universal-flag-configuration", - "id": "1", - "attributes": { - "createdAt": "2024-07-18T00:00:00Z", - "format": "SERVER", - "environment": {"name": "test"}, - "flags": { - "success": { - "key": "success", - "enabled": true, - "variationType": "BOOLEAN", - "variations": {}, - "allocations": [] - }, - "fail_parsing": { - "key": "fail_parsing", - "enabled": true, - "variationType": "NEW_TYPE", - "variations": {}, - "allocations": [] - } - } + "id": "1", + "createdAt": "2024-07-18T00:00:00Z", + "format": "SERVER", + "environment": { + "name": "test" + }, + "flags": { + "success": { + "key": "success", + "enabled": true, + "variationType": "BOOLEAN", + "variations": {}, + "allocations": [] + }, + "fail_parsing": { + "key": "fail_parsing", + "enabled": true, + "variationType": "NEW_TYPE", + "variations": {}, + "allocations": [] } } } @@ -556,20 +485,17 @@ mod tests { ) .unwrap(); assert!( - matches!( - ufc.data.attributes.flags.get("success").unwrap(), - TryParse::Parsed(_) - ), + matches!(ufc.flags.get("success").unwrap(), TryParse::Parsed(_)), "{:?} should match TryParse::Parsed(_)", - ufc.data.attributes.flags.get("success").unwrap() + ufc.flags.get("success").unwrap() ); assert!( matches!( - ufc.data.attributes.flags.get("fail_parsing").unwrap(), + ufc.flags.get("fail_parsing").unwrap(), TryParse::ParseFailed(_) ), "{:?} should match TryParse::ParseFailed(_)", - ufc.data.attributes.flags.get("fail_parsing").unwrap() + ufc.flags.get("fail_parsing").unwrap() ); } } diff --git a/datadog-ffe/tests/data/flags-v1.json b/datadog-ffe/tests/data/flags-v1.json index e8e285b90b..670715638e 100644 --- a/datadog-ffe/tests/data/flags-v1.json +++ b/datadog-ffe/tests/data/flags-v1.json @@ -1,3060 +1,3079 @@ { - "data": { - "type": "universal-flag-configuration", - "id": "1", - "attributes": { - "createdAt": "2024-04-17T19:40:53.716Z", - "format": "SERVER", - "environment": { - "name": "Test" + "id": "1", + "createdAt": "2024-04-17T19:40:53.716Z", + "format": "SERVER", + "environment": { + "name": "Test" + }, + "flags": { + "empty_flag": { + "key": "empty_flag", + "enabled": true, + "variationType": "STRING", + "variations": {}, + "allocations": [] + }, + "disabled_flag": { + "key": "disabled_flag", + "enabled": false, + "variationType": "INTEGER", + "variations": {}, + "allocations": [] + }, + "no_allocations_flag": { + "key": "no_allocations_flag", + "enabled": true, + "variationType": "JSON", + "variations": { + "control": { + "key": "control", + "value": { + "variant": "control" + } + }, + "treatment": { + "key": "treatment", + "value": { + "variant": "treatment" + } + } }, - "flags": { - "empty_flag": { - "key": "empty_flag", - "enabled": true, - "variationType": "STRING", - "variations": {}, - "allocations": [] - }, - "disabled_flag": { - "key": "disabled_flag", - "enabled": false, - "variationType": "INTEGER", - "variations": {}, - "allocations": [] - }, - "no_allocations_flag": { - "key": "no_allocations_flag", - "enabled": true, - "variationType": "JSON", - "variations": { - "control": { - "key": "control", - "value": {"variant": "control"} - }, - "treatment": { - "key": "treatment", - "value": {"variant": "treatment"} - } - }, - "allocations": [] - }, - "numeric_flag": { - "key": "numeric_flag", - "enabled": true, - "variationType": "NUMERIC", - "variations": { - "e": { - "key": "e", - "value": 2.7182818 - }, - "pi": { - "key": "pi", - "value": 3.1415926 + "allocations": [] + }, + "numeric_flag": { + "key": "numeric_flag", + "enabled": true, + "variationType": "NUMERIC", + "variations": { + "e": { + "key": "e", + "value": 2.7182818 + }, + "pi": { + "key": "pi", + "value": 3.1415926 + } + }, + "allocations": [ + { + "key": "rollout", + "splits": [ + { + "variationKey": "pi", + "shards": [] } - }, - "allocations": [ + ], + "doLog": true + } + ] + }, + "regex-flag": { + "key": "regex-flag", + "enabled": true, + "variationType": "STRING", + "variations": { + "partial-example": { + "key": "partial-example", + "value": "partial-example" + }, + "test": { + "key": "test", + "value": "test" + } + }, + "allocations": [ + { + "key": "partial-example", + "rules": [ { - "key": "rollout", - "splits": [ + "conditions": [ { - "variationKey": "pi", - "shards": [] + "attribute": "email", + "operator": "MATCHES", + "value": "@example\\.com" } - ], - "doLog": true - } - ] - }, - "regex-flag": { - "key": "regex-flag", - "enabled": true, - "variationType": "STRING", - "variations": { - "partial-example": { - "key": "partial-example", - "value": "partial-example" - }, - "test": { - "key": "test", - "value": "test" + ] + } + ], + "splits": [ + { + "variationKey": "partial-example", + "shards": [] } - }, - "allocations": [ + ], + "doLog": true + }, + { + "key": "test", + "rules": [ { - "key": "partial-example", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "email", - "operator": "MATCHES", - "value": "@example\\.com" - } - ] + "attribute": "email", + "operator": "MATCHES", + "value": ".*@test\\.com" } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "test", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "numeric-one-of": { + "key": "numeric-one-of", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "1": { + "key": "1", + "value": 1 + }, + "2": { + "key": "2", + "value": 2 + }, + "3": { + "key": "3", + "value": 3 + } + }, + "allocations": [ + { + "key": "1-for-1", + "rules": [ + { + "conditions": [ { - "variationKey": "partial-example", - "shards": [] + "attribute": "number", + "operator": "ONE_OF", + "value": [ + "1" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "1", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "2-for-123456789", + "rules": [ { - "key": "test", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "email", - "operator": "MATCHES", - "value": ".*@test\\.com" - } + "attribute": "number", + "operator": "ONE_OF", + "value": [ + "123456789" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "2", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "3-for-not-2", + "rules": [ + { + "conditions": [ { - "variationKey": "test", - "shards": [] + "attribute": "number", + "operator": "NOT_ONE_OF", + "value": [ + "2" + ] } - ], - "doLog": true - } - ] - }, - "numeric-one-of": { - "key": "numeric-one-of", - "enabled": true, - "variationType": "INTEGER", - "variations": { - "1": { - "key": "1", - "value": 1 - }, - "2": { - "key": "2", - "value": 2 - }, - "3": { - "key": "3", - "value": 3 + ] + } + ], + "splits": [ + { + "variationKey": "3", + "shards": [] } - }, - "allocations": [ + ], + "doLog": true + } + ] + }, + "boolean-one-of-matches": { + "key": "boolean-one-of-matches", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "1": { + "key": "1", + "value": 1 + }, + "2": { + "key": "2", + "value": 2 + }, + "3": { + "key": "3", + "value": 3 + }, + "4": { + "key": "4", + "value": 4 + }, + "5": { + "key": "5", + "value": 5 + } + }, + "allocations": [ + { + "key": "1-for-one-of", + "rules": [ { - "key": "1-for-1", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "number", - "operator": "ONE_OF", - "value": [ - "1" - ] - } + "attribute": "one_of_flag", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "1", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "2-for-matches", + "rules": [ + { + "conditions": [ { - "variationKey": "1", - "shards": [] + "attribute": "matches_flag", + "operator": "MATCHES", + "value": "true" } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "2", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "3-for-not-one-of", + "rules": [ { - "key": "2-for-123456789", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "number", - "operator": "ONE_OF", - "value": [ - "123456789" - ] - } + "attribute": "not_one_of_flag", + "operator": "NOT_ONE_OF", + "value": [ + "false" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "3", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "4-for-not-matches", + "rules": [ + { + "conditions": [ { - "variationKey": "2", - "shards": [] + "attribute": "not_matches_flag", + "operator": "NOT_MATCHES", + "value": "false" } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "4", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "5-for-matches-null", + "rules": [ { - "key": "3-for-not-2", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "number", - "operator": "NOT_ONE_OF", - "value": [ - "2" - ] - } + "attribute": "null_flag", + "operator": "ONE_OF", + "value": [ + "null" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "5", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "empty_string_flag": { + "key": "empty_string_flag", + "enabled": true, + "comment": "Testing the empty string as a variation value", + "variationType": "STRING", + "variations": { + "empty_string": { + "key": "empty_string", + "value": "" + }, + "non_empty": { + "key": "non_empty", + "value": "non_empty" + } + }, + "allocations": [ + { + "key": "allocation-empty", + "rules": [ + { + "conditions": [ { - "variationKey": "3", - "shards": [] + "attribute": "country", + "operator": "MATCHES", + "value": "US" } - ], - "doLog": true - } - ] - }, - "boolean-one-of-matches": { - "key": "boolean-one-of-matches", - "enabled": true, - "variationType": "INTEGER", - "variations": { - "1": { - "key": "1", - "value": 1 - }, - "2": { - "key": "2", - "value": 2 - }, - "3": { - "key": "3", - "value": 3 - }, - "4": { - "key": "4", - "value": 4 - }, - "5": { - "key": "5", - "value": 5 + ] } - }, - "allocations": [ + ], + "splits": [ { - "key": "1-for-one-of", - "rules": [ + "variationKey": "empty_string", + "shards": [ { - "conditions": [ + "salt": "allocation-empty-shards", + "totalShards": 10000, + "ranges": [ { - "attribute": "one_of_flag", - "operator": "ONE_OF", - "value": [ - "true" - ] + "start": 0, + "end": 10000 } ] } - ], - "splits": [ - { - "variationKey": "1", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "doLog": true + }, + { + "key": "allocation-test", + "rules": [], + "splits": [ { - "key": "2-for-matches", - "rules": [ + "variationKey": "non_empty", + "shards": [ { - "conditions": [ + "salt": "allocation-empty-shards", + "totalShards": 10000, + "ranges": [ { - "attribute": "matches_flag", - "operator": "MATCHES", - "value": "true" + "start": 0, + "end": 10000 } ] } - ], - "splits": [ + ] + } + ], + "doLog": true + } + ] + }, + "kill-switch": { + "key": "kill-switch", + "enabled": true, + "variationType": "BOOLEAN", + "variations": { + "on": { + "key": "on", + "value": true + }, + "off": { + "key": "off", + "value": false + } + }, + "allocations": [ + { + "key": "on-for-NA", + "rules": [ + { + "conditions": [ { - "variationKey": "2", - "shards": [] + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "3-for-not-one-of", - "rules": [ + "variationKey": "on", + "shards": [ { - "conditions": [ + "salt": "some-salt", + "totalShards": 10000, + "ranges": [ { - "attribute": "not_one_of_flag", - "operator": "NOT_ONE_OF", - "value": [ - "false" - ] + "start": 0, + "end": 10000 } ] } - ], - "splits": [ + ] + } + ], + "doLog": true + }, + { + "key": "on-for-age-50+", + "rules": [ + { + "conditions": [ { - "variationKey": "3", - "shards": [] + "attribute": "age", + "operator": "GTE", + "value": 50 } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "4-for-not-matches", - "rules": [ + "variationKey": "on", + "shards": [ { - "conditions": [ + "salt": "some-salt", + "totalShards": 10000, + "ranges": [ { - "attribute": "not_matches_flag", - "operator": "NOT_MATCHES", - "value": "false" + "start": 0, + "end": 10000 } ] } - ], - "splits": [ + ] + } + ], + "doLog": true + }, + { + "key": "off-for-all", + "rules": [], + "splits": [ + { + "variationKey": "off", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "comparator-operator-test": { + "key": "comparator-operator-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "small": { + "key": "small", + "value": "small" + }, + "medium": { + "key": "medium", + "value": "medium" + }, + "large": { + "key": "large", + "value": "large" + } + }, + "allocations": [ + { + "key": "small-size", + "rules": [ + { + "conditions": [ { - "variationKey": "4", - "shards": [] + "attribute": "size", + "operator": "LT", + "value": 10 } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "small", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "medum-size", + "rules": [ { - "key": "5-for-matches-null", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "null_flag", - "operator": "ONE_OF", - "value": [ - "null" - ] - } - ] + "attribute": "size", + "operator": "GTE", + "value": 10 + }, + { + "attribute": "size", + "operator": "LTE", + "value": 20 } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "medium", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "large-size", + "rules": [ + { + "conditions": [ { - "variationKey": "5", - "shards": [] + "attribute": "size", + "operator": "GT", + "value": 25 } - ], - "doLog": true - } - ] - }, - "empty_string_flag": { - "key": "empty_string_flag", - "enabled": true, - "comment": "Testing the empty string as a variation value", - "variationType": "STRING", - "variations": { - "empty_string": { - "key": "empty_string", - "value": "" + ] + } + ], + "splits": [ + { + "variationKey": "large", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "start-and-end-date-test": { + "key": "start-and-end-date-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "old": { + "key": "old", + "value": "old" + }, + "current": { + "key": "current", + "value": "current" + }, + "new": { + "key": "new", + "value": "new" + } + }, + "allocations": [ + { + "key": "old-versions", + "splits": [ + { + "variationKey": "old", + "shards": [] + } + ], + "endAt": "2002-10-31T09:00:00.594Z", + "doLog": true + }, + { + "key": "future-versions", + "splits": [ + { + "variationKey": "new", + "shards": [] + } + ], + "startAt": "2052-10-31T09:00:00.594Z", + "doLog": true + }, + { + "key": "current-versions", + "splits": [ + { + "variationKey": "current", + "shards": [] + } + ], + "startAt": "2022-10-31T09:00:00.594Z", + "endAt": "2050-10-31T09:00:00.594Z", + "doLog": true + } + ] + }, + "null-operator-test": { + "key": "null-operator-test", + "enabled": true, + "variationType": "STRING", + "variations": { + "old": { + "key": "old", + "value": "old" + }, + "new": { + "key": "new", + "value": "new" + } + }, + "allocations": [ + { + "key": "null-operator", + "rules": [ + { + "conditions": [ + { + "attribute": "size", + "operator": "IS_NULL", + "value": true + } + ] }, - "non_empty": { - "key": "non_empty", - "value": "non_empty" + { + "conditions": [ + { + "attribute": "size", + "operator": "LT", + "value": 10 + } + ] + } + ], + "splits": [ + { + "variationKey": "old", + "shards": [] } - }, - "allocations": [ + ], + "doLog": true + }, + { + "key": "not-null-operator", + "rules": [ { - "key": "allocation-empty", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "country", - "operator": "MATCHES", - "value": "US" - } + "attribute": "size", + "operator": "IS_NULL", + "value": false + } + ] + } + ], + "splits": [ + { + "variationKey": "new", + "shards": [] + } + ], + "doLog": true + } + ] + }, + "new-user-onboarding": { + "key": "new-user-onboarding", + "enabled": true, + "variationType": "STRING", + "variations": { + "control": { + "key": "control", + "value": "control" + }, + "red": { + "key": "red", + "value": "red" + }, + "blue": { + "key": "blue", + "value": "blue" + }, + "green": { + "key": "green", + "value": "green" + }, + "yellow": { + "key": "yellow", + "value": "yellow" + }, + "purple": { + "key": "purple", + "value": "purple" + } + }, + "allocations": [ + { + "key": "id rule", + "rules": [ + { + "conditions": [ + { + "attribute": "id", + "operator": "MATCHES", + "value": "zach" + } + ] + } + ], + "splits": [ + { + "variationKey": "purple", + "shards": [] + } + ], + "doLog": false + }, + { + "key": "internal users", + "rules": [ + { + "conditions": [ + { + "attribute": "email", + "operator": "MATCHES", + "value": "@mycompany.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "green", + "shards": [] + } + ], + "doLog": false + }, + { + "key": "experiment", + "rules": [ + { + "conditions": [ + { + "attribute": "country", + "operator": "NOT_ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "control", + "shards": [ { - "variationKey": "empty_string", - "shards": [ + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "salt": "allocation-empty-shards", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] + "start": 0, + "end": 6000 } ] - } - ], - "doLog": true - }, - { - "key": "allocation-test", - "rules": [], - "splits": [ + }, { - "variationKey": "non_empty", - "shards": [ + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "salt": "allocation-empty-shards", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] + "start": 0, + "end": 5000 } ] } - ], - "doLog": true - } - ] - }, - "kill-switch": { - "key": "kill-switch", - "enabled": true, - "variationType": "BOOLEAN", - "variations": { - "on": { - "key": "on", - "value": true + ] }, - "off": { - "key": "off", - "value": false - } - }, - "allocations": [ { - "key": "on-for-NA", - "rules": [ + "variationKey": "red", + "shards": [ { - "conditions": [ + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "attribute": "country", - "operator": "ONE_OF", - "value": [ - "US", - "Canada", - "Mexico" - ] + "start": 0, + "end": 6000 } ] - } - ], - "splits": [ + }, { - "variationKey": "on", - "shards": [ + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "salt": "some-salt", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] + "start": 5000, + "end": 8000 } ] } - ], - "doLog": true + ] }, { - "key": "on-for-age-50+", - "rules": [ + "variationKey": "yellow", + "shards": [ { - "conditions": [ + "salt": "traffic-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "attribute": "age", - "operator": "GTE", - "value": 50 + "start": 0, + "end": 6000 } ] - } - ], - "splits": [ + }, { - "variationKey": "on", - "shards": [ + "salt": "split-new-user-onboarding-experiment", + "totalShards": 10000, + "ranges": [ { - "salt": "some-salt", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] + "start": 8000, + "end": 10000 } ] } - ], - "doLog": true - }, + ] + } + ], + "doLog": true + }, + { + "key": "rollout", + "rules": [ { - "key": "off-for-all", - "rules": [], - "splits": [ + "conditions": [ { - "variationKey": "off", - "shards": [] + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] } - ], - "doLog": true - } - ] - }, - "comparator-operator-test": { - "key": "comparator-operator-test", - "enabled": true, - "variationType": "STRING", - "variations": { - "small": { - "key": "small", - "value": "small" - }, - "medium": { - "key": "medium", - "value": "medium" - }, - "large": { - "key": "large", - "value": "large" + ] } - }, - "allocations": [ + ], + "splits": [ { - "key": "small-size", - "rules": [ + "variationKey": "blue", + "shards": [ { - "conditions": [ + "salt": "split-new-user-onboarding-rollout", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "LT", - "value": 10 + "start": 0, + "end": 8000 } ] } ], - "splits": [ + "extraLogging": { + "allocationvalue_type": "rollout", + "owner": "hippo" + } + } + ], + "doLog": true + } + ] + }, + "integer-flag": { + "key": "integer-flag", + "enabled": true, + "variationType": "INTEGER", + "variations": { + "one": { + "key": "one", + "value": 1 + }, + "two": { + "key": "two", + "value": 2 + }, + "three": { + "key": "three", + "value": 3 + } + }, + "allocations": [ + { + "key": "targeted allocation", + "rules": [ + { + "conditions": [ { - "variationKey": "small", - "shards": [] + "attribute": "country", + "operator": "ONE_OF", + "value": [ + "US", + "Canada", + "Mexico" + ] } - ], - "doLog": true + ] }, { - "key": "medum-size", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "size", - "operator": "GTE", - "value": 10 - }, + "attribute": "email", + "operator": "MATCHES", + "value": ".*@example.com" + } + ] + } + ], + "splits": [ + { + "variationKey": "three", + "shards": [ + { + "salt": "full-range-salt", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "LTE", - "value": 20 + "start": 0, + "end": 10000 } ] } - ], - "splits": [ - { - "variationKey": "medium", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "doLog": true + }, + { + "key": "50/50 split", + "rules": [], + "splits": [ { - "key": "large-size", - "rules": [ + "variationKey": "one", + "shards": [ { - "conditions": [ + "salt": "split-numeric-flag-some-allocation", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "GT", - "value": 25 + "start": 0, + "end": 5000 } ] } - ], - "splits": [ - { - "variationKey": "large", - "shards": [] - } - ], - "doLog": true - } - ] - }, - "start-and-end-date-test": { - "key": "start-and-end-date-test", - "enabled": true, - "variationType": "STRING", - "variations": { - "old": { - "key": "old", - "value": "old" - }, - "current": { - "key": "current", - "value": "current" + ] }, - "new": { - "key": "new", - "value": "new" - } - }, - "allocations": [ { - "key": "old-versions", - "splits": [ + "variationKey": "two", + "shards": [ { - "variationKey": "old", - "shards": [] + "salt": "split-numeric-flag-some-allocation", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 10000 + } + ] } - ], - "endAt": "2002-10-31T09:00:00.594Z", - "doLog": true - }, + ] + } + ], + "doLog": true + } + ] + }, + "json-config-flag": { + "key": "json-config-flag", + "enabled": true, + "variationType": "JSON", + "variations": { + "one": { + "key": "one", + "value": { + "integer": 1, + "string": "one", + "float": 1.0 + } + }, + "two": { + "key": "two", + "value": { + "integer": 2, + "string": "two", + "float": 2.0 + } + }, + "empty": { + "key": "empty", + "value": {} + } + }, + "allocations": [ + { + "key": "Optionally Force Empty", + "rules": [ { - "key": "future-versions", - "splits": [ + "conditions": [ { - "variationKey": "new", - "shards": [] + "attribute": "Force Empty", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "startAt": "2052-10-31T09:00:00.594Z", - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "current-versions", - "splits": [ + "variationKey": "empty", + "shards": [ { - "variationKey": "current", - "shards": [] + "salt": "full-range-salt", + "totalShards": 10000, + "ranges": [ + { + "start": 0, + "end": 10000 + } + ] } - ], - "startAt": "2022-10-31T09:00:00.594Z", - "endAt": "2050-10-31T09:00:00.594Z", - "doLog": true - } - ] - }, - "null-operator-test": { - "key": "null-operator-test", - "enabled": true, - "variationType": "STRING", - "variations": { - "old": { - "key": "old", - "value": "old" - }, - "new": { - "key": "new", - "value": "new" + ] } - }, - "allocations": [ + ], + "doLog": true + }, + { + "key": "50/50 split", + "rules": [], + "splits": [ { - "key": "null-operator", - "rules": [ + "variationKey": "one", + "shards": [ { - "conditions": [ + "salt": "traffic-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "IS_NULL", - "value": true + "start": 0, + "end": 10000 } ] }, { - "conditions": [ + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "LT", - "value": 10 + "start": 0, + "end": 5000 } ] } - ], - "splits": [ - { - "variationKey": "old", - "shards": [] - } - ], - "doLog": true + ] }, { - "key": "not-null-operator", - "rules": [ + "variationKey": "two", + "shards": [ { - "conditions": [ + "salt": "traffic-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "size", - "operator": "IS_NULL", - "value": false + "start": 0, + "end": 10000 } ] - } - ], - "splits": [ + }, { - "variationKey": "new", - "shards": [] + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ + { + "start": 5000, + "end": 10000 + } + ] } - ], - "doLog": true - } - ] - }, - "new-user-onboarding": { - "key": "new-user-onboarding", - "enabled": true, - "variationType": "STRING", - "variations": { - "control": { - "key": "control", - "value": "control" - }, - "red": { - "key": "red", - "value": "red" - }, - "blue": { - "key": "blue", - "value": "blue" - }, - "green": { - "key": "green", - "value": "green" - }, - "yellow": { - "key": "yellow", - "value": "yellow" - }, - "purple": { - "key": "purple", - "value": "purple" + ] } - }, - "allocations": [ + ], + "doLog": true + } + ] + }, + "special-characters": { + "key": "special-characters", + "enabled": true, + "variationType": "JSON", + "variations": { + "de": { + "key": "de", + "value": { + "a": "kümmert", + "b": "schön" + } + }, + "ua": { + "key": "ua", + "value": { + "a": "піклуватися", + "b": "любов" + } + }, + "zh": { + "key": "zh", + "value": { + "a": "照顾", + "b": "漂亮" + } + }, + "emoji": { + "key": "emoji", + "value": { + "a": "🤗", + "b": "🌸" + } + } + }, + "allocations": [ + { + "key": "allocation-test", + "splits": [ { - "key": "id rule", - "rules": [ + "variationKey": "de", + "shards": [ { - "conditions": [ + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "id", - "operator": "MATCHES", - "value": "zach" + "start": 0, + "end": 2500 } ] } - ], - "splits": [ - { - "variationKey": "purple", - "shards": [] - } - ], - "doLog": false + ] }, { - "key": "internal users", - "rules": [ + "variationKey": "ua", + "shards": [ { - "conditions": [ + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "email", - "operator": "MATCHES", - "value": "@mycompany.com" + "start": 2500, + "end": 5000 } ] } - ], - "splits": [ - { - "variationKey": "green", - "shards": [] - } - ], - "doLog": false + ] }, { - "key": "experiment", - "rules": [ + "variationKey": "zh", + "shards": [ { - "conditions": [ + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ { - "attribute": "country", - "operator": "NOT_ONE_OF", - "value": [ - "US", - "Canada", - "Mexico" - ] + "start": 5000, + "end": 7500 } ] } - ], - "splits": [ + ] + }, + { + "variationKey": "emoji", + "shards": [ { - "variationKey": "control", - "shards": [ - { - "salt": "traffic-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 6000 - } - ] - }, + "salt": "split-json-flag", + "totalShards": 10000, + "ranges": [ { - "salt": "split-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 5000 - } - ] + "start": 7500, + "end": 10000 } ] - }, + } + ] + } + ], + "doLog": true + }, + { + "key": "allocation-default", + "splits": [ + { + "variationKey": "de", + "shards": [] + } + ], + "doLog": false + } + ] + }, + "string_flag_with_special_characters": { + "key": "string_flag_with_special_characters", + "enabled": true, + "comment": "Testing the string with special characters and spaces", + "variationType": "STRING", + "variations": { + "string_with_spaces": { + "key": "string_with_spaces", + "value": " a b c d e f " + }, + "string_with_only_one_space": { + "key": "string_with_only_one_space", + "value": " " + }, + "string_with_only_multiple_spaces": { + "key": "string_with_only_multiple_spaces", + "value": " " + }, + "string_with_dots": { + "key": "string_with_dots", + "value": ".a.b.c.d.e.f." + }, + "string_with_only_one_dot": { + "key": "string_with_only_one_dot", + "value": "." + }, + "string_with_only_multiple_dots": { + "key": "string_with_only_multiple_dots", + "value": "......." + }, + "string_with_comas": { + "key": "string_with_comas", + "value": ",a,b,c,d,e,f," + }, + "string_with_only_one_coma": { + "key": "string_with_only_one_coma", + "value": "," + }, + "string_with_only_multiple_comas": { + "key": "string_with_only_multiple_comas", + "value": ",,,,,,," + }, + "string_with_colons": { + "key": "string_with_colons", + "value": ":a:b:c:d:e:f:" + }, + "string_with_only_one_colon": { + "key": "string_with_only_one_colon", + "value": ":" + }, + "string_with_only_multiple_colons": { + "key": "string_with_only_multiple_colons", + "value": ":::::::" + }, + "string_with_semicolons": { + "key": "string_with_semicolons", + "value": ";a;b;c;d;e;f;" + }, + "string_with_only_one_semicolon": { + "key": "string_with_only_one_semicolon", + "value": ";" + }, + "string_with_only_multiple_semicolons": { + "key": "string_with_only_multiple_semicolons", + "value": ";;;;;;;" + }, + "string_with_slashes": { + "key": "string_with_slashes", + "value": "/a/b/c/d/e/f/" + }, + "string_with_only_one_slash": { + "key": "string_with_only_one_slash", + "value": "/" + }, + "string_with_only_multiple_slashes": { + "key": "string_with_only_multiple_slashes", + "value": "///////" + }, + "string_with_dashes": { + "key": "string_with_dashes", + "value": "-a-b-c-d-e-f-" + }, + "string_with_only_one_dash": { + "key": "string_with_only_one_dash", + "value": "-" + }, + "string_with_only_multiple_dashes": { + "key": "string_with_only_multiple_dashes", + "value": "-------" + }, + "string_with_underscores": { + "key": "string_with_underscores", + "value": "_a_b_c_d_e_f_" + }, + "string_with_only_one_underscore": { + "key": "string_with_only_one_underscore", + "value": "_" + }, + "string_with_only_multiple_underscores": { + "key": "string_with_only_multiple_underscores", + "value": "_______" + }, + "string_with_plus_signs": { + "key": "string_with_plus_signs", + "value": "+a+b+c+d+e+f+" + }, + "string_with_only_one_plus_sign": { + "key": "string_with_only_one_plus_sign", + "value": "+" + }, + "string_with_only_multiple_plus_signs": { + "key": "string_with_only_multiple_plus_signs", + "value": "+++++++" + }, + "string_with_equal_signs": { + "key": "string_with_equal_signs", + "value": "=a=b=c=d=e=f=" + }, + "string_with_only_one_equal_sign": { + "key": "string_with_only_one_equal_sign", + "value": "=" + }, + "string_with_only_multiple_equal_signs": { + "key": "string_with_only_multiple_equal_signs", + "value": "=======" + }, + "string_with_dollar_signs": { + "key": "string_with_dollar_signs", + "value": "$a$b$c$d$e$f$" + }, + "string_with_only_one_dollar_sign": { + "key": "string_with_only_one_dollar_sign", + "value": "$" + }, + "string_with_only_multiple_dollar_signs": { + "key": "string_with_only_multiple_dollar_signs", + "value": "$$$$$$$" + }, + "string_with_at_signs": { + "key": "string_with_at_signs", + "value": "@a@b@c@d@e@f@" + }, + "string_with_only_one_at_sign": { + "key": "string_with_only_one_at_sign", + "value": "@" + }, + "string_with_only_multiple_at_signs": { + "key": "string_with_only_multiple_at_signs", + "value": "@@@@@@@" + }, + "string_with_amp_signs": { + "key": "string_with_amp_signs", + "value": "&a&b&c&d&e&f&" + }, + "string_with_only_one_amp_sign": { + "key": "string_with_only_one_amp_sign", + "value": "&" + }, + "string_with_only_multiple_amp_signs": { + "key": "string_with_only_multiple_amp_signs", + "value": "&&&&&&&" + }, + "string_with_hash_signs": { + "key": "string_with_hash_signs", + "value": "#a#b#c#d#e#f#" + }, + "string_with_only_one_hash_sign": { + "key": "string_with_only_one_hash_sign", + "value": "#" + }, + "string_with_only_multiple_hash_signs": { + "key": "string_with_only_multiple_hash_signs", + "value": "#######" + }, + "string_with_percentage_signs": { + "key": "string_with_percentage_signs", + "value": "%a%b%c%d%e%f%" + }, + "string_with_only_one_percentage_sign": { + "key": "string_with_only_one_percentage_sign", + "value": "%" + }, + "string_with_only_multiple_percentage_signs": { + "key": "string_with_only_multiple_percentage_signs", + "value": "%%%%%%%" + }, + "string_with_tilde_signs": { + "key": "string_with_tilde_signs", + "value": "~a~b~c~d~e~f~" + }, + "string_with_only_one_tilde_sign": { + "key": "string_with_only_one_tilde_sign", + "value": "~" + }, + "string_with_only_multiple_tilde_signs": { + "key": "string_with_only_multiple_tilde_signs", + "value": "~~~~~~~" + }, + "string_with_asterix_signs": { + "key": "string_with_asterix_signs", + "value": "*a*b*c*d*e*f*" + }, + "string_with_only_one_asterix_sign": { + "key": "string_with_only_one_asterix_sign", + "value": "*" + }, + "string_with_only_multiple_asterix_signs": { + "key": "string_with_only_multiple_asterix_signs", + "value": "*******" + }, + "string_with_single_quotes": { + "key": "string_with_single_quotes", + "value": "'a'b'c'd'e'f'" + }, + "string_with_only_one_single_quote": { + "key": "string_with_only_one_single_quote", + "value": "'" + }, + "string_with_only_multiple_single_quotes": { + "key": "string_with_only_multiple_single_quotes", + "value": "'''''''" + }, + "string_with_question_marks": { + "key": "string_with_question_marks", + "value": "?a?b?c?d?e?f?" + }, + "string_with_only_one_question_mark": { + "key": "string_with_only_one_question_mark", + "value": "?" + }, + "string_with_only_multiple_question_marks": { + "key": "string_with_only_multiple_question_marks", + "value": "???????" + }, + "string_with_exclamation_marks": { + "key": "string_with_exclamation_marks", + "value": "!a!b!c!d!e!f!" + }, + "string_with_only_one_exclamation_mark": { + "key": "string_with_only_one_exclamation_mark", + "value": "!" + }, + "string_with_only_multiple_exclamation_marks": { + "key": "string_with_only_multiple_exclamation_marks", + "value": "!!!!!!!" + }, + "string_with_opening_parentheses": { + "key": "string_with_opening_parentheses", + "value": "(a(b(c(d(e(f(" + }, + "string_with_only_one_opening_parenthese": { + "key": "string_with_only_one_opening_parenthese", + "value": "(" + }, + "string_with_only_multiple_opening_parentheses": { + "key": "string_with_only_multiple_opening_parentheses", + "value": "(((((((" + }, + "string_with_closing_parentheses": { + "key": "string_with_closing_parentheses", + "value": ")a)b)c)d)e)f)" + }, + "string_with_only_one_closing_parenthese": { + "key": "string_with_only_one_closing_parenthese", + "value": ")" + }, + "string_with_only_multiple_closing_parentheses": { + "key": "string_with_only_multiple_closing_parentheses", + "value": ")))))))" + } + }, + "allocations": [ + { + "key": "allocation-test-string_with_spaces", + "rules": [ + { + "conditions": [ { - "variationKey": "red", - "shards": [ - { - "salt": "traffic-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 6000 - } - ] - }, - { - "salt": "split-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 5000, - "end": 8000 - } - ] - } + "attribute": "string_with_spaces", + "operator": "ONE_OF", + "value": [ + "true" ] - }, + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_spaces", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_space", + "rules": [ + { + "conditions": [ { - "variationKey": "yellow", - "shards": [ - { - "salt": "traffic-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 6000 - } - ] - }, - { - "salt": "split-new-user-onboarding-experiment", - "totalShards": 10000, - "ranges": [ - { - "start": 8000, - "end": 10000 - } - ] - } + "attribute": "string_with_only_one_space", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_space", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_spaces", + "rules": [ { - "key": "rollout", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "country", - "operator": "ONE_OF", - "value": [ - "US", - "Canada", - "Mexico" - ] - } + "attribute": "string_with_only_multiple_spaces", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_spaces", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dots", + "rules": [ + { + "conditions": [ { - "variationKey": "blue", - "shards": [ - { - "salt": "split-new-user-onboarding-rollout", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 8000 - } - ] - } - ], - "extraLogging": { - "allocationvalue_type": "rollout", - "owner": "hippo" - } + "attribute": "string_with_dots", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - } - ] - }, - "integer-flag": { - "key": "integer-flag", - "enabled": true, - "variationType": "INTEGER", - "variations": { - "one": { - "key": "one", - "value": 1 - }, - "two": { - "key": "two", - "value": 2 - }, - "three": { - "key": "three", - "value": 3 + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dots", + "shards": [] } - }, - "allocations": [ + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dot", + "rules": [ { - "key": "targeted allocation", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "country", - "operator": "ONE_OF", - "value": [ - "US", - "Canada", - "Mexico" - ] - } + "attribute": "string_with_only_one_dot", + "operator": "ONE_OF", + "value": [ + "true" ] - }, + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dot", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dots", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "email", - "operator": "MATCHES", - "value": ".*@example.com" - } + "attribute": "string_with_only_multiple_dots", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_dots", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_comas", + "rules": [ + { + "conditions": [ { - "variationKey": "three", - "shards": [ - { - "salt": "full-range-salt", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] - } + "attribute": "string_with_comas", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_comas", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_coma", + "rules": [ { - "key": "50/50 split", - "rules": [], - "splits": [ + "conditions": [ { - "variationKey": "one", - "shards": [ - { - "salt": "split-numeric-flag-some-allocation", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 5000 - } - ] - } + "attribute": "string_with_only_one_coma", + "operator": "ONE_OF", + "value": [ + "true" ] - }, + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_coma", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_comas", + "rules": [ + { + "conditions": [ { - "variationKey": "two", - "shards": [ - { - "salt": "split-numeric-flag-some-allocation", - "totalShards": 10000, - "ranges": [ - { - "start": 5000, - "end": 10000 - } - ] - } + "attribute": "string_with_only_multiple_comas", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "doLog": true - } - ] - }, - "json-config-flag": { - "key": "json-config-flag", - "enabled": true, - "variationType": "JSON", - "variations": { - "one": { - "key": "one", - "value": { "integer": 1, "string": "one", "float": 1.0 } - }, - "two": { - "key": "two", - "value": { "integer": 2, "string": "two", "float": 2.0 } - }, - "empty": { - "key": "empty", - "value": {} + ] } - }, - "allocations": [ + ], + "splits": [ { - "key": "Optionally Force Empty", - "rules": [ + "variationKey": "string_with_only_multiple_comas", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_colons", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "Force Empty", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_colons", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_colons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_colon", + "rules": [ + { + "conditions": [ { - "variationKey": "empty", - "shards": [ - { - "salt": "full-range-salt", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] - } + "attribute": "string_with_only_one_colon", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "50/50 split", - "rules": [], - "splits": [ + "variationKey": "string_with_only_one_colon", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_colons", + "rules": [ + { + "conditions": [ { - "variationKey": "one", - "shards": [ - { - "salt": "traffic-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] - }, - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 5000 - } - ] - } + "attribute": "string_with_only_multiple_colons", + "operator": "ONE_OF", + "value": [ + "true" ] - }, + } + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_colons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_semicolons", + "rules": [ + { + "conditions": [ { - "variationKey": "two", - "shards": [ - { - "salt": "traffic-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 10000 - } - ] - }, - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 5000, - "end": 10000 - } - ] - } + "attribute": "string_with_semicolons", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "doLog": true - } - ] - }, - "special-characters": { - "key": "special-characters", - "enabled": true, - "variationType": "JSON", - "variations": { - "de": { - "key": "de", - "value": {"a": "kümmert", "b": "schön"} - }, - "ua": { - "key": "ua", - "value": {"a": "піклуватися", "b": "любов"} - }, - "zh": { - "key": "zh", - "value": {"a": "照顾", "b": "漂亮"} - }, - "emoji": { - "key": "emoji", - "value": {"a": "🤗", "b": "🌸"} + ] } - }, - "allocations": [ + ], + "splits": [ { - "key": "allocation-test", - "splits": [ + "variationKey": "string_with_semicolons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_semicolon", + "rules": [ + { + "conditions": [ { - "variationKey": "de", - "shards": [ - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 0, - "end": 2500 - } - ] - } - ] - }, - { - "variationKey": "ua", - "shards": [ - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 2500, - "end": 5000 - } - ] - } - ] - }, - { - "variationKey": "zh", - "shards": [ - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 5000, - "end": 7500 - } - ] - } - ] - }, - { - "variationKey": "emoji", - "shards": [ - { - "salt": "split-json-flag", - "totalShards": 10000, - "ranges": [ - { - "start": 7500, - "end": 10000 - } - ] - } - ] - } - ], - "doLog": true - }, - { - "key": "allocation-default", - "splits": [ - { - "variationKey": "de", - "shards": [] - } - ], - "doLog": false - } - ] - }, - "string_flag_with_special_characters": { - "key": "string_flag_with_special_characters", - "enabled": true, - "comment": "Testing the string with special characters and spaces", - "variationType": "STRING", - "variations": { - "string_with_spaces": { - "key": "string_with_spaces", - "value": " a b c d e f " - }, - "string_with_only_one_space": { - "key": "string_with_only_one_space", - "value": " " - }, - "string_with_only_multiple_spaces": { - "key": "string_with_only_multiple_spaces", - "value": " " - }, - "string_with_dots": { - "key": "string_with_dots", - "value": ".a.b.c.d.e.f." - }, - "string_with_only_one_dot": { - "key": "string_with_only_one_dot", - "value": "." - }, - "string_with_only_multiple_dots": { - "key": "string_with_only_multiple_dots", - "value": "......." - }, - "string_with_comas": { - "key": "string_with_comas", - "value": ",a,b,c,d,e,f," - }, - "string_with_only_one_coma": { - "key": "string_with_only_one_coma", - "value": "," - }, - "string_with_only_multiple_comas": { - "key": "string_with_only_multiple_comas", - "value": ",,,,,,," - }, - "string_with_colons": { - "key": "string_with_colons", - "value": ":a:b:c:d:e:f:" - }, - "string_with_only_one_colon": { - "key": "string_with_only_one_colon", - "value": ":" - }, - "string_with_only_multiple_colons": { - "key": "string_with_only_multiple_colons", - "value": ":::::::" - }, - "string_with_semicolons": { - "key": "string_with_semicolons", - "value": ";a;b;c;d;e;f;" - }, - "string_with_only_one_semicolon": { - "key": "string_with_only_one_semicolon", - "value": ";" - }, - "string_with_only_multiple_semicolons": { - "key": "string_with_only_multiple_semicolons", - "value": ";;;;;;;" - }, - "string_with_slashes": { - "key": "string_with_slashes", - "value": "/a/b/c/d/e/f/" - }, - "string_with_only_one_slash": { - "key": "string_with_only_one_slash", - "value": "/" - }, - "string_with_only_multiple_slashes": { - "key": "string_with_only_multiple_slashes", - "value": "///////" - }, - "string_with_dashes": { - "key": "string_with_dashes", - "value": "-a-b-c-d-e-f-" - }, - "string_with_only_one_dash": { - "key": "string_with_only_one_dash", - "value": "-" - }, - "string_with_only_multiple_dashes": { - "key": "string_with_only_multiple_dashes", - "value": "-------" - }, - "string_with_underscores": { - "key": "string_with_underscores", - "value": "_a_b_c_d_e_f_" - }, - "string_with_only_one_underscore": { - "key": "string_with_only_one_underscore", - "value": "_" - }, - "string_with_only_multiple_underscores": { - "key": "string_with_only_multiple_underscores", - "value": "_______" - }, - "string_with_plus_signs": { - "key": "string_with_plus_signs", - "value": "+a+b+c+d+e+f+" - }, - "string_with_only_one_plus_sign": { - "key": "string_with_only_one_plus_sign", - "value": "+" - }, - "string_with_only_multiple_plus_signs": { - "key": "string_with_only_multiple_plus_signs", - "value": "+++++++" - }, - "string_with_equal_signs": { - "key": "string_with_equal_signs", - "value": "=a=b=c=d=e=f=" - }, - "string_with_only_one_equal_sign": { - "key": "string_with_only_one_equal_sign", - "value": "=" - }, - "string_with_only_multiple_equal_signs": { - "key": "string_with_only_multiple_equal_signs", - "value": "=======" - }, - "string_with_dollar_signs": { - "key": "string_with_dollar_signs", - "value": "$a$b$c$d$e$f$" - }, - "string_with_only_one_dollar_sign": { - "key": "string_with_only_one_dollar_sign", - "value": "$" - }, - "string_with_only_multiple_dollar_signs": { - "key": "string_with_only_multiple_dollar_signs", - "value": "$$$$$$$" - }, - "string_with_at_signs": { - "key": "string_with_at_signs", - "value": "@a@b@c@d@e@f@" - }, - "string_with_only_one_at_sign": { - "key": "string_with_only_one_at_sign", - "value": "@" - }, - "string_with_only_multiple_at_signs": { - "key": "string_with_only_multiple_at_signs", - "value": "@@@@@@@" - }, - "string_with_amp_signs": { - "key": "string_with_amp_signs", - "value": "&a&b&c&d&e&f&" - }, - "string_with_only_one_amp_sign": { - "key": "string_with_only_one_amp_sign", - "value": "&" - }, - "string_with_only_multiple_amp_signs": { - "key": "string_with_only_multiple_amp_signs", - "value": "&&&&&&&" - }, - "string_with_hash_signs": { - "key": "string_with_hash_signs", - "value": "#a#b#c#d#e#f#" - }, - "string_with_only_one_hash_sign": { - "key": "string_with_only_one_hash_sign", - "value": "#" - }, - "string_with_only_multiple_hash_signs": { - "key": "string_with_only_multiple_hash_signs", - "value": "#######" - }, - "string_with_percentage_signs": { - "key": "string_with_percentage_signs", - "value": "%a%b%c%d%e%f%" - }, - "string_with_only_one_percentage_sign": { - "key": "string_with_only_one_percentage_sign", - "value": "%" - }, - "string_with_only_multiple_percentage_signs": { - "key": "string_with_only_multiple_percentage_signs", - "value": "%%%%%%%" - }, - "string_with_tilde_signs": { - "key": "string_with_tilde_signs", - "value": "~a~b~c~d~e~f~" - }, - "string_with_only_one_tilde_sign": { - "key": "string_with_only_one_tilde_sign", - "value": "~" - }, - "string_with_only_multiple_tilde_signs": { - "key": "string_with_only_multiple_tilde_signs", - "value": "~~~~~~~" - }, - "string_with_asterix_signs": { - "key": "string_with_asterix_signs", - "value": "*a*b*c*d*e*f*" - }, - "string_with_only_one_asterix_sign": { - "key": "string_with_only_one_asterix_sign", - "value": "*" - }, - "string_with_only_multiple_asterix_signs": { - "key": "string_with_only_multiple_asterix_signs", - "value": "*******" - }, - "string_with_single_quotes": { - "key": "string_with_single_quotes", - "value": "'a'b'c'd'e'f'" - }, - "string_with_only_one_single_quote": { - "key": "string_with_only_one_single_quote", - "value": "'" - }, - "string_with_only_multiple_single_quotes": { - "key": "string_with_only_multiple_single_quotes", - "value": "'''''''" - }, - "string_with_question_marks": { - "key": "string_with_question_marks", - "value": "?a?b?c?d?e?f?" - }, - "string_with_only_one_question_mark": { - "key": "string_with_only_one_question_mark", - "value": "?" - }, - "string_with_only_multiple_question_marks": { - "key": "string_with_only_multiple_question_marks", - "value": "???????" - }, - "string_with_exclamation_marks": { - "key": "string_with_exclamation_marks", - "value": "!a!b!c!d!e!f!" - }, - "string_with_only_one_exclamation_mark": { - "key": "string_with_only_one_exclamation_mark", - "value": "!" - }, - "string_with_only_multiple_exclamation_marks": { - "key": "string_with_only_multiple_exclamation_marks", - "value": "!!!!!!!" - }, - "string_with_opening_parentheses": { - "key": "string_with_opening_parentheses", - "value": "(a(b(c(d(e(f(" - }, - "string_with_only_one_opening_parenthese": { - "key": "string_with_only_one_opening_parenthese", - "value": "(" - }, - "string_with_only_multiple_opening_parentheses": { - "key": "string_with_only_multiple_opening_parentheses", - "value": "(((((((" - }, - "string_with_closing_parentheses": { - "key": "string_with_closing_parentheses", - "value": ")a)b)c)d)e)f)" - }, - "string_with_only_one_closing_parenthese": { - "key": "string_with_only_one_closing_parenthese", - "value": ")" - }, - "string_with_only_multiple_closing_parentheses": { - "key": "string_with_only_multiple_closing_parentheses", - "value": ")))))))" - } - }, - "allocations": [ - { - "key": "allocation-test-string_with_spaces", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_spaces", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_spaces", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_space", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_space", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_space", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_spaces", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_spaces", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_spaces", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_dots", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_dots", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_dots", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_dot", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_dot", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_dot", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_dots", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_dots", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_dots", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_comas", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_comas", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_comas", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_coma", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_coma", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_coma", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_comas", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_comas", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_comas", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_colons", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_colons", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_colons", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_colon", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_colon", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_colon", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_colons", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_colons", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_colons", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_semicolons", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_semicolons", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_semicolons", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_semicolon", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_semicolon", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_semicolon", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_semicolons", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_semicolons", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_semicolons", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_slashes", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_slashes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_slashes", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_slash", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_slash", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_slash", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_slashes", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_slashes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_slashes", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_dashes", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_dashes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_dashes", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_dash", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_dash", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_dash", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_dashes", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_dashes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_dashes", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_underscores", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_underscores", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_underscores", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_underscore", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_underscore", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_underscore", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_multiple_underscores", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_underscores", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_underscores", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_plus_signs", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_plus_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_plus_signs", - "shards": [] - } - ], - "doLog": true - }, - { - "key": "allocation-test-string_with_only_one_plus_sign", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_plus_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_semicolon", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_plus_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_multiple_plus_signs", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_plus_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_plus_signs", - "shards": [] - } - ], - "doLog": true - }, + "variationKey": "string_with_only_one_semicolon", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_semicolons", + "rules": [ { - "key": "allocation-test-string_with_equal_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_equal_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_semicolons", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_equal_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_one_equal_sign", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_equal_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_equal_sign", - "shards": [] - } - ], - "doLog": true - }, + "variationKey": "string_with_only_multiple_semicolons", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_slashes", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_equal_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_equal_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_slashes", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_equal_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_dollar_signs", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_dollar_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_dollar_signs", - "shards": [] - } - ], - "doLog": true - }, + "variationKey": "string_with_slashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_slash", + "rules": [ { - "key": "allocation-test-string_with_only_one_dollar_sign", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_dollar_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_slash", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_dollar_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_multiple_dollar_signs", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_multiple_dollar_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_dollar_signs", - "shards": [] - } - ], - "doLog": true - }, + "variationKey": "string_with_only_one_slash", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_slashes", + "rules": [ { - "key": "allocation-test-string_with_at_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_at_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_slashes", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_at_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_one_at_sign", - "rules": [ - { - "conditions": [ - { - "attribute": "string_with_only_one_at_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } - ] - } - ], - "splits": [ - { - "variationKey": "string_with_only_one_at_sign", - "shards": [] - } - ], - "doLog": true - }, + "variationKey": "string_with_only_multiple_slashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dashes", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_at_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_at_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_dashes", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_at_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dash", + "rules": [ { - "key": "allocation-test-string_with_amp_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_amp_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_dash", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_amp_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dash", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dashes", + "rules": [ { - "key": "allocation-test-string_with_only_one_amp_sign", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_amp_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_dashes", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_amp_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_multiple_amp_signs", - "rules": [ + "variationKey": "string_with_only_multiple_dashes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_underscores", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_amp_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_underscores", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_amp_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_underscores", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_underscore", + "rules": [ { - "key": "allocation-test-string_with_hash_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_hash_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_underscore", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_hash_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_underscore", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_underscores", + "rules": [ { - "key": "allocation-test-string_with_only_one_hash_sign", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_hash_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_underscores", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_hash_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_underscores", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_plus_signs", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_hash_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_hash_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_plus_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_hash_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_percentage_signs", - "rules": [ + "variationKey": "string_with_plus_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_plus_sign", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_percentage_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_plus_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_percentage_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_plus_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_plus_signs", + "rules": [ { - "key": "allocation-test-string_with_only_one_percentage_sign", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_percentage_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_plus_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_percentage_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_plus_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_equal_signs", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_percentage_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_percentage_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_equal_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_percentage_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_equal_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_equal_sign", + "rules": [ { - "key": "allocation-test-string_with_tilde_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_tilde_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_equal_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_tilde_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_one_tilde_sign", - "rules": [ + "variationKey": "string_with_only_one_equal_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_equal_signs", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_tilde_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_equal_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_one_tilde_sign", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_equal_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_dollar_signs", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_tilde_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_tilde_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_dollar_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_only_multiple_tilde_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_dollar_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_dollar_sign", + "rules": [ { - "key": "allocation-test-string_with_asterix_signs", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_asterix_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_dollar_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ - { - "variationKey": "string_with_asterix_signs", - "shards": [] - } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_dollar_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_dollar_signs", + "rules": [ { - "key": "allocation-test-string_with_only_one_asterix_sign", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_asterix_sign", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_dollar_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_dollar_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_at_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_asterix_sign", - "shards": [] + "attribute": "string_with_at_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_multiple_asterix_signs", - "rules": [ + "variationKey": "string_with_at_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_at_sign", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_asterix_signs", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_at_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_at_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_at_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_asterix_signs", - "shards": [] + "attribute": "string_with_only_multiple_at_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_at_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_amp_signs", + "rules": [ { - "key": "allocation-test-string_with_single_quotes", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_single_quotes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_amp_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_amp_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_amp_sign", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_single_quotes", - "shards": [] + "attribute": "string_with_only_one_amp_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_amp_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_amp_signs", + "rules": [ { - "key": "allocation-test-string_with_only_one_single_quote", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_single_quote", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_amp_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_amp_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_hash_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_single_quote", - "shards": [] + "attribute": "string_with_hash_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_hash_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_hash_sign", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_single_quotes", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_single_quotes", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_hash_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_hash_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_hash_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_single_quotes", - "shards": [] + "attribute": "string_with_only_multiple_hash_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_question_marks", - "rules": [ + "variationKey": "string_with_only_multiple_hash_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_percentage_signs", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_question_marks", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_percentage_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_percentage_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_percentage_sign", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_question_marks", - "shards": [] + "attribute": "string_with_only_one_percentage_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_percentage_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_percentage_signs", + "rules": [ { - "key": "allocation-test-string_with_only_one_question_mark", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_question_mark", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_percentage_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_percentage_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_tilde_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_question_mark", - "shards": [] + "attribute": "string_with_tilde_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_tilde_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_tilde_sign", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_question_marks", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_question_marks", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_tilde_sign", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_tilde_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_tilde_signs", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_question_marks", - "shards": [] + "attribute": "string_with_only_multiple_tilde_signs", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_tilde_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_asterix_signs", + "rules": [ { - "key": "allocation-test-string_with_exclamation_marks", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_exclamation_marks", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_asterix_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_asterix_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_asterix_sign", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_exclamation_marks", - "shards": [] + "attribute": "string_with_only_one_asterix_sign", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_one_exclamation_mark", - "rules": [ + "variationKey": "string_with_only_one_asterix_sign", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_asterix_signs", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_exclamation_mark", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_asterix_signs", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_asterix_signs", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_single_quotes", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_exclamation_mark", - "shards": [] + "attribute": "string_with_single_quotes", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_single_quotes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_single_quote", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_exclamation_marks", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_exclamation_marks", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_single_quote", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_single_quote", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_single_quotes", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_exclamation_marks", - "shards": [] + "attribute": "string_with_only_multiple_single_quotes", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_single_quotes", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_question_marks", + "rules": [ { - "key": "allocation-test-string_with_opening_parentheses", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_opening_parentheses", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_question_marks", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_question_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_question_mark", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_opening_parentheses", - "shards": [] + "attribute": "string_with_only_one_question_mark", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_question_mark", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_question_marks", + "rules": [ { - "key": "allocation-test-string_with_only_one_opening_parenthese", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_opening_parenthese", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_question_marks", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_question_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_exclamation_marks", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_opening_parenthese", - "shards": [] + "attribute": "string_with_exclamation_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ { - "key": "allocation-test-string_with_only_multiple_opening_parentheses", - "rules": [ + "variationKey": "string_with_exclamation_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_exclamation_mark", + "rules": [ + { + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_opening_parentheses", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_exclamation_mark", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_exclamation_mark", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_exclamation_marks", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_opening_parentheses", - "shards": [] + "attribute": "string_with_only_multiple_exclamation_marks", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_exclamation_marks", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_opening_parentheses", + "rules": [ { - "key": "allocation-test-string_with_closing_parentheses", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_closing_parentheses", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_opening_parentheses", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_opening_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_opening_parenthese", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_closing_parentheses", - "shards": [] + "attribute": "string_with_only_one_opening_parenthese", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_opening_parenthese", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_opening_parentheses", + "rules": [ { - "key": "allocation-test-string_with_only_one_closing_parenthese", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_one_closing_parenthese", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_multiple_opening_parentheses", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_opening_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_closing_parentheses", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_one_closing_parenthese", - "shards": [] + "attribute": "string_with_closing_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true - }, + ] + } + ], + "splits": [ + { + "variationKey": "string_with_closing_parentheses", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_one_closing_parenthese", + "rules": [ { - "key": "allocation-test-string_with_only_multiple_closing_parentheses", - "rules": [ + "conditions": [ { - "conditions": [ - { - "attribute": "string_with_only_multiple_closing_parentheses", - "operator": "ONE_OF", - "value": [ - "true" - ] - } + "attribute": "string_with_only_one_closing_parenthese", + "operator": "ONE_OF", + "value": [ + "true" ] } - ], - "splits": [ + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_one_closing_parenthese", + "shards": [] + } + ], + "doLog": true + }, + { + "key": "allocation-test-string_with_only_multiple_closing_parentheses", + "rules": [ + { + "conditions": [ { - "variationKey": "string_with_only_multiple_closing_parentheses", - "shards": [] + "attribute": "string_with_only_multiple_closing_parentheses", + "operator": "ONE_OF", + "value": [ + "true" + ] } - ], - "doLog": true + ] + } + ], + "splits": [ + { + "variationKey": "string_with_only_multiple_closing_parentheses", + "shards": [] } - ] + ], + "doLog": true } - } + ] } } }