From 16ce50479912e0636201bff7743661dbd81a9d70 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Thu, 4 Jul 2024 13:49:17 +0200 Subject: [PATCH 01/16] experimental(rule-condition): Add loop rule condition --- relay-protocol/src/condition.rs | 135 +++++++++++++++++++++++++++++++- relay-protocol/src/value.rs | 13 ++- 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index b35ede0933..3c0f31bebe 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -307,6 +307,87 @@ impl NotCondition { } } +/// Combines multiple conditions using logical AND. +/// +/// This condition matches if **all** of the inner conditions match. The default value for this +/// condition is `true`, that is, this rule matches if there are no inner conditions. +/// +/// See [`RuleCondition::and`]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LoopCondition { + /// Path of the field that should match the value. + pub name: String, + /// Inner rule to match on each element. + pub inner: Box, +} + +impl LoopCondition { + fn supported(&self) -> bool { + self.inner.supported() + } + fn matches(&self, instance: &T) -> bool + where + T: Getter + ?Sized, + { + let Some(Val::Array(arr)) = instance.get_value(self.name.as_str()) else { + return false; + }; + + for i in 0..arr.length { + let prefix = format!("{}.{}", self.name.as_str(), i); + // TODO: we might want to explore how to keep a single cloned copy and mutate that + // in place each time. + let mut inner = self.inner.clone(); + apply_prefix(prefix.as_str(), inner.as_mut()); + if inner.matches(instance) { + return true; + } + } + + false + } +} + +fn apply_prefix(prefix: &str, condition: &mut RuleCondition) { + match condition { + RuleCondition::Eq(eq) => { + eq.name = format!("{}.{}", prefix, eq.name); + } + RuleCondition::Gte(gte) => { + gte.name = format!("{}.{}", prefix, gte.name); + } + RuleCondition::Lte(lte) => { + lte.name = format!("{}.{}", prefix, lte.name); + } + RuleCondition::Gt(gt) => { + gt.name = format!("{}.{}", prefix, gt.name); + } + RuleCondition::Lt(lt) => { + lt.name = format!("{}.{}", prefix, lt.name); + } + RuleCondition::Glob(glob) => { + glob.name = format!("{}.{}", prefix, glob.name); + } + RuleCondition::Or(or) => { + for element in or.inner.iter_mut() { + apply_prefix(prefix, element); + } + } + RuleCondition::And(and) => { + for element in and.inner.iter_mut() { + apply_prefix(prefix, element); + } + } + RuleCondition::Not(not) => { + apply_prefix(prefix, not.inner.as_mut()); + } + RuleCondition::Loop(_loop) => { + apply_prefix(prefix, _loop.inner.as_mut()); + } + RuleCondition::Unsupported => {} + } +} + /// A condition that can be evaluated on structured data. /// /// The basic conditions are [`eq`](Self::eq), [`glob`](Self::glob), and the comparison operators. @@ -445,6 +526,15 @@ pub enum RuleCondition { /// ``` Not(NotCondition), + /// Loops over an array field and returns true if at least one element matches + /// the inner condition. + /// + /// # Example + /// + /// ``` + /// ``` + Loop(LoopCondition), + /// An unsupported condition for future compatibility. #[serde(other)] Unsupported, @@ -655,6 +745,7 @@ impl RuleCondition { RuleCondition::And(rules) => rules.supported(), RuleCondition::Or(rules) => rules.supported(), RuleCondition::Not(rule) => rule.supported(), + RuleCondition::Loop(rule) => rule.supported(), } } @@ -673,6 +764,7 @@ impl RuleCondition { RuleCondition::And(conditions) => conditions.matches(value), RuleCondition::Or(conditions) => conditions.matches(value), RuleCondition::Not(condition) => condition.matches(value), + RuleCondition::Loop(condition) => condition.matches(value), RuleCondition::Unsupported => false, } } @@ -705,21 +797,45 @@ impl std::ops::Not for RuleCondition { #[cfg(test)] mod tests { use super::*; + use crate::{Annotated, Array}; + + struct Exception { + name: String, + } struct MockDSC { transaction: String, release: String, environment: String, user_segment: String, + exceptions: Array, } impl Getter for MockDSC { fn get_value(&self, path: &str) -> Option> { - Some(match path.strip_prefix("trace.")? { + let path = path.strip_prefix("trace.")?; + let mut components = path.split('.'); + + Some(match components.next()? { "transaction" => self.transaction.as_str().into(), "release" => self.release.as_str().into(), "environment" => self.environment.as_str().into(), - "user.segment" => self.user_segment.as_str().into(), + "user" => match components.next()? { + "segment" => self.user_segment.as_str().into(), + _ => return None, + }, + "exceptions" => { + if let Some(value) = components.next() { + let index: usize = value.parse().ok()?; + let v = self.exceptions.get(index)?.value()?; + match components.next()? { + "name" => v.name.as_str().into(), + _ => return None, + } + } else { + (&self.exceptions).into() + } + } _ => { return None; } @@ -733,6 +849,9 @@ mod tests { release: "1.1.1".to_string(), environment: "debug".to_string(), user_segment: "vip".to_string(), + exceptions: vec![Annotated::new(Exception { + name: "NullPointerException".to_string(), + })], } } @@ -1093,4 +1212,16 @@ mod tests { assert!(!condition.matches(&dsc), "{failure_name}"); } } + + #[test] + fn test_loop_condition() { + let condition = RuleCondition::Loop(LoopCondition { + name: "trace.exceptions".to_string(), + inner: Box::new(RuleCondition::glob("name", "*Exception")), + }); + + let dsc = mock_dsc(); + + assert_eq!(condition.matches(&dsc), true); + } } diff --git a/relay-protocol/src/value.rs b/relay-protocol/src/value.rs index e5c7b9f5eb..bd8f3010b1 100644 --- a/relay-protocol/src/value.rs +++ b/relay-protocol/src/value.rs @@ -354,6 +354,7 @@ where /// Borrowed version of [`Array`]. #[derive(Debug, Clone, Copy)] pub struct Arr<'a> { + pub length: usize, _phantom: std::marker::PhantomData<&'a ()>, } @@ -493,7 +494,8 @@ impl<'a> From<&'a Value> for Val<'a> { Value::U64(value) => Self::U64(*value), Value::F64(value) => Self::F64(*value), Value::String(value) => Self::String(value), - Value::Array(_) => Self::Array(Arr { + Value::Array(value) => Self::Array(Arr { + length: value.len(), _phantom: Default::default(), }), Value::Object(_) => Self::Object(Obj { @@ -503,6 +505,15 @@ impl<'a> From<&'a Value> for Val<'a> { } } +impl<'a, T> From<&'a Array> for Val<'a> { + fn from(value: &'a Array) -> Self { + Self::Array(Arr { + length: value.len(), + _phantom: Default::default(), + }) + } +} + impl PartialEq for Val<'_> { fn eq(&self, other: &Self) -> bool { match (self, other) { From 0d1ed880bbd7a26898c0337c83de4fc9eabb23e4 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Thu, 4 Jul 2024 15:46:11 +0200 Subject: [PATCH 02/16] Fix --- relay-event-schema/src/protocol/event.rs | 21 +++- relay-protocol/src/condition.rs | 154 ++++++++++------------- relay-protocol/src/traits.rs | 37 ++++++ relay-protocol/src/value.rs | 21 +--- 4 files changed, 131 insertions(+), 102 deletions(-) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 3ce586f51b..7905915f54 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -4,7 +4,9 @@ use std::str::FromStr; use relay_common::time; #[cfg(feature = "jsonschema")] use relay_jsonschema_derive::JsonSchema; -use relay_protocol::{Annotated, Array, Empty, FromValue, Getter, IntoValue, Object, Val, Value}; +use relay_protocol::{ + Annotated, Arr, Array, Empty, FromValue, Getter, GetterIter, IntoValue, Object, Val, Value, +}; #[cfg(feature = "jsonschema")] use schemars::{gen::SchemaGenerator, schema::Schema}; use sentry_release_parser::Release as ParsedRelease; @@ -805,6 +807,23 @@ impl Getter for Event { } }) } + + fn get_iter(&self, path: &str) -> Option> { + Some(match path.strip_prefix("event.")? { + "exceptions" => GetterIter::new_annotated(self.exceptions.value()?.values.value()?), + _ => return None, + }) + } +} + +impl Getter for Exception { + fn get_value(&self, path: &str) -> Option> { + Some(match path { + "ty" => self.ty.as_str()?.into(), + "value" => self.value.as_str()?.into(), + _ => return None, + }) + } } #[cfg(test)] diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index 3c0f31bebe..db80793ba0 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -5,6 +5,7 @@ use relay_common::glob3::GlobPatterns; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::fmt::format; use crate::{Getter, Val}; @@ -307,21 +308,16 @@ impl NotCondition { } } -/// Combines multiple conditions using logical AND. -/// -/// This condition matches if **all** of the inner conditions match. The default value for this -/// condition is `true`, that is, this rule matches if there are no inner conditions. -/// -/// See [`RuleCondition::and`]. +/// TODO: add comments. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct LoopCondition { +pub struct AnyCondition { /// Path of the field that should match the value. pub name: String, /// Inner rule to match on each element. pub inner: Box, } -impl LoopCondition { +impl AnyCondition { fn supported(&self) -> bool { self.inner.supported() } @@ -329,62 +325,36 @@ impl LoopCondition { where T: Getter + ?Sized, { - let Some(Val::Array(arr)) = instance.get_value(self.name.as_str()) else { + let Some(mut getter_iter) = instance.get_iter(self.name.as_str()) else { return false; }; - for i in 0..arr.length { - let prefix = format!("{}.{}", self.name.as_str(), i); - // TODO: we might want to explore how to keep a single cloned copy and mutate that - // in place each time. - let mut inner = self.inner.clone(); - apply_prefix(prefix.as_str(), inner.as_mut()); - if inner.matches(instance) { - return true; - } - } - - false + getter_iter.any(|g| self.inner.matches(g)) } } -fn apply_prefix(prefix: &str, condition: &mut RuleCondition) { - match condition { - RuleCondition::Eq(eq) => { - eq.name = format!("{}.{}", prefix, eq.name); - } - RuleCondition::Gte(gte) => { - gte.name = format!("{}.{}", prefix, gte.name); - } - RuleCondition::Lte(lte) => { - lte.name = format!("{}.{}", prefix, lte.name); - } - RuleCondition::Gt(gt) => { - gt.name = format!("{}.{}", prefix, gt.name); - } - RuleCondition::Lt(lt) => { - lt.name = format!("{}.{}", prefix, lt.name); - } - RuleCondition::Glob(glob) => { - glob.name = format!("{}.{}", prefix, glob.name); - } - RuleCondition::Or(or) => { - for element in or.inner.iter_mut() { - apply_prefix(prefix, element); - } - } - RuleCondition::And(and) => { - for element in and.inner.iter_mut() { - apply_prefix(prefix, element); - } - } - RuleCondition::Not(not) => { - apply_prefix(prefix, not.inner.as_mut()); - } - RuleCondition::Loop(_loop) => { - apply_prefix(prefix, _loop.inner.as_mut()); - } - RuleCondition::Unsupported => {} +/// TODO: add comments. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AllCondition { + /// Path of the field that should match the value. + pub name: String, + /// Inner rule to match on each element. + pub inner: Box, +} + +impl AllCondition { + fn supported(&self) -> bool { + self.inner.supported() + } + fn matches(&self, instance: &T) -> bool + where + T: Getter + ?Sized, + { + let Some(mut getter_iter) = instance.get_iter(self.name.as_str()) else { + return false; + }; + + getter_iter.all(|g| self.inner.matches(g)) } } @@ -529,11 +499,22 @@ pub enum RuleCondition { /// Loops over an array field and returns true if at least one element matches /// the inner condition. /// + /// # TODO: impl docs. + /// # Example + /// + /// ``` + /// ``` + Any(AnyCondition), + + /// Loops over an array field and returns true if at least one element matches + /// the inner condition. + /// + /// # TODO: impl docs. /// # Example /// /// ``` /// ``` - Loop(LoopCondition), + All(AllCondition), /// An unsupported condition for future compatibility. #[serde(other)] @@ -745,7 +726,8 @@ impl RuleCondition { RuleCondition::And(rules) => rules.supported(), RuleCondition::Or(rules) => rules.supported(), RuleCondition::Not(rule) => rule.supported(), - RuleCondition::Loop(rule) => rule.supported(), + RuleCondition::Any(rule) => rule.supported(), + RuleCondition::All(rule) => rule.supported(), } } @@ -764,7 +746,8 @@ impl RuleCondition { RuleCondition::And(conditions) => conditions.matches(value), RuleCondition::Or(conditions) => conditions.matches(value), RuleCondition::Not(condition) => condition.matches(value), - RuleCondition::Loop(condition) => condition.matches(value), + RuleCondition::Any(condition) => condition.matches(value), + RuleCondition::All(condition) => condition.matches(value), RuleCondition::Unsupported => false, } } @@ -797,50 +780,49 @@ impl std::ops::Not for RuleCondition { #[cfg(test)] mod tests { use super::*; - use crate::{Annotated, Array}; + use crate::{Annotated, Array, GetterIter}; + #[derive(Debug)] struct Exception { name: String, } + impl Getter for Exception { + fn get_value(&self, path: &str) -> Option> { + Some(match path { + "name" => self.name.as_str().into(), + _ => return None, + }) + } + } + struct MockDSC { transaction: String, release: String, environment: String, user_segment: String, - exceptions: Array, + exceptions: Vec, } impl Getter for MockDSC { fn get_value(&self, path: &str) -> Option> { - let path = path.strip_prefix("trace.")?; - let mut components = path.split('.'); - - Some(match components.next()? { + Some(match path.strip_prefix("trace.")? { "transaction" => self.transaction.as_str().into(), "release" => self.release.as_str().into(), "environment" => self.environment.as_str().into(), - "user" => match components.next()? { - "segment" => self.user_segment.as_str().into(), - _ => return None, - }, - "exceptions" => { - if let Some(value) = components.next() { - let index: usize = value.parse().ok()?; - let v = self.exceptions.get(index)?.value()?; - match components.next()? { - "name" => v.name.as_str().into(), - _ => return None, - } - } else { - (&self.exceptions).into() - } - } + "user.segment" => self.user_segment.as_str().into(), _ => { return None; } }) } + + fn get_iter(&self, path: &str) -> Option> { + Some(match path.strip_prefix("trace.")? { + "exceptions" => GetterIter::new(self.exceptions.iter()), + _ => return None, + }) + } } fn mock_dsc() -> MockDSC { @@ -849,9 +831,9 @@ mod tests { release: "1.1.1".to_string(), environment: "debug".to_string(), user_segment: "vip".to_string(), - exceptions: vec![Annotated::new(Exception { + exceptions: vec![Exception { name: "NullPointerException".to_string(), - })], + }], } } @@ -1215,7 +1197,7 @@ mod tests { #[test] fn test_loop_condition() { - let condition = RuleCondition::Loop(LoopCondition { + let condition = RuleCondition::Loop(AnyCondition { name: "trace.exceptions".to_string(), inner: Box::new(RuleCondition::glob("name", "*Exception")), }); diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index 0658a038e8..2e7f83471c 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -125,6 +125,39 @@ pub trait IntoValue: Debug + Empty { } } +pub struct GetterIter<'a> { + iter: Box + 'a>, +} + +impl<'a> GetterIter<'a> { + pub fn new(iterator: I) -> Self + where + I: Iterator + 'a, + T: 'a + Getter, + { + Self { + iter: Box::new(iterator.map(|v| v as &dyn Getter)), + } + } + + pub fn new_annotated(iterator: I) -> Self + where + I: IntoIterator>, + I::IntoIter: 'a, + T: 'static + Getter, + { + Self::new(iterator.into_iter().filter_map(Annotated::value)) + } +} + +impl<'a> Iterator for GetterIter<'a> { + type Item = &'a dyn Getter; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + /// A type that supports field access by paths. /// /// This is the runtime version of [`get_value!`](crate::get_value!) and additionally supports @@ -195,4 +228,8 @@ pub trait IntoValue: Debug + Empty { pub trait Getter { /// Returns the serialized value of a field pointed to by a `path`. fn get_value(&self, path: &str) -> Option>; + + fn get_iter(&self, path: &str) -> Option> { + None + } } diff --git a/relay-protocol/src/value.rs b/relay-protocol/src/value.rs index bd8f3010b1..c453fc0e3c 100644 --- a/relay-protocol/src/value.rs +++ b/relay-protocol/src/value.rs @@ -1,14 +1,16 @@ -use std::collections::BTreeMap; -use std::{fmt, str}; - #[cfg(feature = "jsonschema")] use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::de::{Deserialize, MapAccess, SeqAccess, Visitor}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::{fmt, str}; use uuid::Uuid; use crate::annotated::Annotated; use crate::meta::Meta; +use crate::Getter; /// Alias for typed arrays. pub type Array = Vec>; @@ -354,7 +356,6 @@ where /// Borrowed version of [`Array`]. #[derive(Debug, Clone, Copy)] pub struct Arr<'a> { - pub length: usize, _phantom: std::marker::PhantomData<&'a ()>, } @@ -494,8 +495,7 @@ impl<'a> From<&'a Value> for Val<'a> { Value::U64(value) => Self::U64(*value), Value::F64(value) => Self::F64(*value), Value::String(value) => Self::String(value), - Value::Array(value) => Self::Array(Arr { - length: value.len(), + Value::Array(_) => Self::Array(Arr { _phantom: Default::default(), }), Value::Object(_) => Self::Object(Obj { @@ -505,15 +505,6 @@ impl<'a> From<&'a Value> for Val<'a> { } } -impl<'a, T> From<&'a Array> for Val<'a> { - fn from(value: &'a Array) -> Self { - Self::Array(Arr { - length: value.len(), - _phantom: Default::default(), - }) - } -} - impl PartialEq for Val<'_> { fn eq(&self, other: &Self) -> bool { match (self, other) { From 51a995099a567093c224932dcffaecd1077aaa50 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 11:11:33 +0200 Subject: [PATCH 03/16] Improve --- relay-event-schema/src/protocol/event.rs | 10 - relay-event-schema/src/protocol/exception.rs | 12 +- relay-protocol/src/condition.rs | 312 +++++++++++++------ relay-protocol/src/traits.rs | 72 ++++- relay-protocol/src/value.rs | 2 - 5 files changed, 304 insertions(+), 104 deletions(-) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 7905915f54..37210ff0b9 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -816,16 +816,6 @@ impl Getter for Event { } } -impl Getter for Exception { - fn get_value(&self, path: &str) -> Option> { - Some(match path { - "ty" => self.ty.as_str()?.into(), - "value" => self.value.as_str()?.into(), - _ => return None, - }) - } -} - #[cfg(test)] mod tests { use chrono::{TimeZone, Utc}; diff --git a/relay-event-schema/src/protocol/exception.rs b/relay-event-schema/src/protocol/exception.rs index 7fa07cc727..e6ef5fcfa0 100644 --- a/relay-event-schema/src/protocol/exception.rs +++ b/relay-event-schema/src/protocol/exception.rs @@ -1,6 +1,6 @@ #[cfg(feature = "jsonschema")] use relay_jsonschema_derive::JsonSchema; -use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value}; +use relay_protocol::{Annotated, Empty, FromValue, Getter, IntoValue, Object, Val, Value}; use crate::processor::ProcessValue; use crate::protocol::{JsonLenientString, Mechanism, RawStacktrace, Stacktrace, ThreadId}; @@ -72,6 +72,16 @@ pub struct Exception { pub other: Object, } +impl Getter for Exception { + fn get_value(&self, path: &str) -> Option> { + Some(match path { + "ty" => self.ty.as_str()?.into(), + "value" => self.value.as_str()?.into(), + _ => return None, + }) + } +} + #[cfg(test)] mod tests { use relay_protocol::Map; diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index db80793ba0..e089ca2f91 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -5,7 +5,6 @@ use relay_common::glob3::GlobPatterns; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::fmt::format; use crate::{Getter, Val}; @@ -308,7 +307,10 @@ impl NotCondition { } } -/// TODO: add comments. +/// Applies the ANY operation to an array field. +/// +/// This condition matches if at least one of the elements of the array match with the +/// `inner` condition. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AnyCondition { /// Path of the field that should match the value. @@ -318,6 +320,14 @@ pub struct AnyCondition { } impl AnyCondition { + /// Creates a condition that matches any element in the array against the `inner` condition. + pub fn new(field: impl Into, inner: RuleCondition) -> Self { + Self { + name: field.into(), + inner: Box::new(inner), + } + } + fn supported(&self) -> bool { self.inner.supported() } @@ -333,7 +343,9 @@ impl AnyCondition { } } -/// TODO: add comments. +/// Applies the ALL operation to an array field. +/// +/// This condition matches if all the elements of the array match with the `inner` condition. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AllCondition { /// Path of the field that should match the value. @@ -343,6 +355,15 @@ pub struct AllCondition { } impl AllCondition { + /// Creates a condition that matches all the elements in the array against the `inner` + /// condition. + pub fn new(field: impl Into, inner: RuleCondition) -> Self { + Self { + name: field.into(), + inner: Box::new(inner), + } + } + fn supported(&self) -> bool { self.inner.supported() } @@ -499,20 +520,27 @@ pub enum RuleCondition { /// Loops over an array field and returns true if at least one element matches /// the inner condition. /// - /// # TODO: impl docs. /// # Example /// /// ``` + /// use relay_protocol::RuleCondition; + /// + /// let condition = RuleCondition::for_any("event.exceptions", + /// RuleCondition::eq("name", "NullPointerException") + /// ); /// ``` Any(AnyCondition), - /// Loops over an array field and returns true if at least one element matches - /// the inner condition. + /// Loops over an array field and returns true if all elements match the inner condition. /// - /// # TODO: impl docs. /// # Example /// /// ``` + /// use relay_protocol::RuleCondition; + /// + /// let condition = RuleCondition::for_all("event.exceptions", + /// RuleCondition::eq("name", "NullPointerException") + /// ); /// ``` All(AllCondition), @@ -709,6 +737,36 @@ impl RuleCondition { } } + /// Creates a [`AnyCondition`]. + /// + /// # Example + /// + /// ``` + /// use relay_protocol::RuleCondition; + /// + /// let condition = RuleCondition::for_any("event.exceptions", + /// RuleCondition::eq("name", "NullPointerException") + /// ); + /// ``` + pub fn for_any(field: impl Into, inner: RuleCondition) -> Self { + Self::Any(AnyCondition::new(field, inner)) + } + + /// Creates a [`AllCondition`]. + /// + /// # Example + /// + /// ``` + /// use relay_protocol::RuleCondition; + /// + /// let condition = RuleCondition::for_all("event.exceptions", + /// RuleCondition::eq("name", "NullPointerException") + /// ); + /// ``` + pub fn for_all(field: impl Into, inner: RuleCondition) -> Self { + Self::All(AllCondition::new(field, inner)) + } + /// Checks if Relay supports this condition (in other words if the condition had any unknown configuration /// which was serialized as "Unsupported" (because the configuration is either faulty or was created for a /// newer relay that supports some other condition types) @@ -780,7 +838,7 @@ impl std::ops::Not for RuleCondition { #[cfg(test)] mod tests { use super::*; - use crate::{Annotated, Array, GetterIter}; + use crate::{Array, GetterIter}; #[derive(Debug)] struct Exception { @@ -796,7 +854,7 @@ mod tests { } } - struct MockDSC { + struct Trace { transaction: String, release: String, environment: String, @@ -804,7 +862,7 @@ mod tests { exceptions: Vec, } - impl Getter for MockDSC { + impl Getter for Trace { fn get_value(&self, path: &str) -> Option> { Some(match path.strip_prefix("trace.")? { "transaction" => self.transaction.as_str().into(), @@ -825,15 +883,20 @@ mod tests { } } - fn mock_dsc() -> MockDSC { - MockDSC { + fn mock_trace() -> Trace { + Trace { transaction: "transaction1".to_string(), release: "1.1.1".to_string(), environment: "debug".to_string(), user_segment: "vip".to_string(), - exceptions: vec![Exception { - name: "NullPointerException".to_string(), - }], + exceptions: vec![ + Exception { + name: "NullPointerException".to_string(), + }, + Exception { + name: "NullUser".to_string(), + }, + ], } } @@ -881,76 +944,117 @@ mod tests { "name": "field_6", "value": ["3.*"] }] + }, + { + "op": "any", + "name": "event.exceptions", + "inner": { + "op": "glob", + "name": "value", + "value": ["*Exception"] + } + }, + { + "op": "all", + "name": "event.exceptions", + "inner": { + "op": "glob", + "name": "value", + "value": ["*Exception"] + } } ]"#; let rules: Result, _> = serde_json::from_str(serialized_rules); assert!(rules.is_ok()); let rules = rules.unwrap(); - insta::assert_ron_snapshot!(rules, @r#" - [ - EqCondition( - op: "eq", - name: "field_1", - value: [ - "UPPER", - "lower", - ], - options: EqCondOptions( - ignoreCase: true, - ), - ), - EqCondition( - op: "eq", - name: "field_2", - value: [ - "UPPER", - "lower", - ], - ), + insta::assert_ron_snapshot!(rules, @r###" + [ + EqCondition( + op: "eq", + name: "field_1", + value: [ + "UPPER", + "lower", + ], + options: EqCondOptions( + ignoreCase: true, + ), + ), + EqCondition( + op: "eq", + name: "field_2", + value: [ + "UPPER", + "lower", + ], + ), + GlobCondition( + op: "glob", + name: "field_3", + value: [ + "1.2.*", + "2.*", + ], + ), + NotCondition( + op: "not", + inner: GlobCondition( + op: "glob", + name: "field_4", + value: [ + "1.*", + ], + ), + ), + AndCondition( + op: "and", + inner: [ GlobCondition( op: "glob", - name: "field_3", + name: "field_5", value: [ - "1.2.*", "2.*", ], ), - NotCondition( - op: "not", - inner: GlobCondition( - op: "glob", - name: "field_4", - value: [ - "1.*", - ], - ), - ), - AndCondition( - op: "and", - inner: [ - GlobCondition( - op: "glob", - name: "field_5", - value: [ - "2.*", - ], - ), - ], - ), - OrCondition( - op: "or", - inner: [ - GlobCondition( - op: "glob", - name: "field_6", - value: [ - "3.*", - ], - ), + ], + ), + OrCondition( + op: "or", + inner: [ + GlobCondition( + op: "glob", + name: "field_6", + value: [ + "3.*", ], ), - ]"#); + ], + ), + AnyCondition( + op: "any", + name: "event.exceptions", + inner: GlobCondition( + op: "glob", + name: "value", + value: [ + "*Exception", + ], + ), + ), + AllCondition( + op: "all", + name: "event.exceptions", + inner: GlobCondition( + op: "glob", + name: "value", + value: [ + "*Exception", + ], + ), + ), + ] + "###); } #[test] @@ -1040,11 +1144,11 @@ mod tests { ("string cmp", RuleCondition::gt("trace.transaction", "t")), ]; - let dsc = mock_dsc(); + let trace = mock_trace(); for (rule_test_name, condition) in conditions.iter() { let failure_name = format!("Failed on test: '{rule_test_name}'!!!"); - assert!(condition.matches(&dsc), "{failure_name}"); + assert!(condition.matches(&trace), "{failure_name}"); } } @@ -1083,11 +1187,11 @@ mod tests { ("never", false, RuleCondition::never()), ]; - let dsc = mock_dsc(); + let trace = mock_trace(); for (rule_test_name, expected, condition) in conditions.iter() { let failure_name = format!("Failed on test: '{rule_test_name}'!!!"); - assert!(condition.matches(&dsc) == *expected, "{failure_name}"); + assert!(condition.matches(&trace) == *expected, "{failure_name}"); } } @@ -1126,11 +1230,11 @@ mod tests { ("all", true, RuleCondition::all()), ]; - let dsc = mock_dsc(); + let trace = mock_trace(); for (rule_test_name, expected, condition) in conditions.iter() { let failure_name = format!("Failed on test: '{rule_test_name}'!!!"); - assert!(condition.matches(&dsc) == *expected, "{failure_name}"); + assert!(condition.matches(&trace) == *expected, "{failure_name}"); } } @@ -1149,11 +1253,11 @@ mod tests { ), ]; - let dsc = mock_dsc(); + let trace = mock_trace(); for (rule_test_name, expected, condition) in conditions.iter() { let failure_name = format!("Failed on test: '{rule_test_name}'!!!"); - assert!(condition.matches(&dsc) == *expected, "{failure_name}"); + assert!(condition.matches(&trace) == *expected, "{failure_name}"); } } @@ -1187,23 +1291,55 @@ mod tests { ), ]; - let dsc = mock_dsc(); + let trace = mock_trace(); for (rule_test_name, condition) in conditions.iter() { let failure_name = format!("Failed on test: '{rule_test_name}'!!!"); - assert!(!condition.matches(&dsc), "{failure_name}"); + assert!(!condition.matches(&trace), "{failure_name}"); } } #[test] - fn test_loop_condition() { - let condition = RuleCondition::Loop(AnyCondition { - name: "trace.exceptions".to_string(), - inner: Box::new(RuleCondition::glob("name", "*Exception")), - }); + fn test_any_condition_with_match() { + let condition = RuleCondition::for_any( + "trace.exceptions", + RuleCondition::glob("name", "*Exception"), + ); + + let trace = mock_trace(); + + assert!(condition.matches(&trace)); + } + + #[test] + fn test_any_condition_with_no_match() { + let condition = + RuleCondition::for_any("trace.exceptions", RuleCondition::glob("name", "Error")); + + let trace = mock_trace(); + + assert!(!condition.matches(&trace)); + } + + #[test] + fn test_all_condition() { + let condition = + RuleCondition::for_all("trace.exceptions", RuleCondition::glob("name", "Null*")); + + let trace = mock_trace(); + + assert!(condition.matches(&trace)); + } + + #[test] + fn test_all_condition_with_no_match() { + let condition = RuleCondition::for_all( + "trace.exceptions", + RuleCondition::glob("name", "*Exception"), + ); - let dsc = mock_dsc(); + let trace = mock_trace(); - assert_eq!(condition.matches(&dsc), true); + assert!(!condition.matches(&trace)); } } diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index 2e7f83471c..299446cbf8 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -125,26 +125,90 @@ pub trait IntoValue: Debug + Empty { } } +/// Hides an iterator of [`Getter`]s that is used to iterate over arbitrary [`Getter`] +/// implementations. +/// +/// # Example +/// +/// ``` +/// use relay_protocol::{Getter, GetterIter, Val}; +/// +/// struct Exception { +/// name: String, +/// } +/// +/// impl Getter for Exception { +/// fn get_value(&self, path: &str) -> Option> { +/// Some(match path { +/// "name" => self.name.as_str().into(), +/// _ => return None, +/// }) +/// } +/// } +/// +/// struct Error { +/// platform: String, +/// exceptions: Vec, +/// } +/// +/// impl Getter for Error { +/// fn get_value(&self, path: &str) -> Option> { +/// Some(match path.strip_prefix("error.")? { +/// "platform" => self.platform.as_str().into(), +/// _ => { +/// return None; +/// } +/// }) +/// } +/// +/// // `get_iter` should return a `GetterIter` that can be used for iterating on the +/// // `Getter`(s). +/// fn get_iter(&self, path: &str) -> Option> { +/// Some(match path.strip_prefix("error.")? { +/// "exceptions" => GetterIter::new(self.exceptions.iter()), +/// _ => return None, +/// }) +/// } +/// } +/// +/// // An example usage given an instance that implement `Getter`. +/// fn matches(instance: &T) -> bool +/// where T: Getter + ?Sized +/// { +/// let Some(mut getter_iter) = instance.get_iter("error.exceptions") else { +/// return false; +/// }; +/// +/// for getter in getter_iter { +/// let exception_name = getter.get_value("name"); +/// } +/// +/// true +/// } +/// ``` pub struct GetterIter<'a> { iter: Box + 'a>, } impl<'a> GetterIter<'a> { + /// Creates a new [`GetterIter`] given an iterator of a type that implements [`Getter`]. pub fn new(iterator: I) -> Self where I: Iterator + 'a, - T: 'a + Getter, + T: Getter + 'a, { Self { iter: Box::new(iterator.map(|v| v as &dyn Getter)), } } + /// Creates a new [`GetterIter`] given an element that can be converted into an iterator of + /// [`Annotated`]s whose type implement [`Getter`]. pub fn new_annotated(iterator: I) -> Self where I: IntoIterator>, I::IntoIter: 'a, - T: 'static + Getter, + T: Getter + 'a, { Self::new(iterator.into_iter().filter_map(Annotated::value)) } @@ -229,7 +293,9 @@ pub trait Getter { /// Returns the serialized value of a field pointed to by a `path`. fn get_value(&self, path: &str) -> Option>; - fn get_iter(&self, path: &str) -> Option> { + /// Returns a [`GetterIter`] that allows iteration on [`Getter`] implementations + /// of fields pointed on by `path`. + fn get_iter(&self, _path: &str) -> Option> { None } } diff --git a/relay-protocol/src/value.rs b/relay-protocol/src/value.rs index c453fc0e3c..e0fdf56566 100644 --- a/relay-protocol/src/value.rs +++ b/relay-protocol/src/value.rs @@ -4,13 +4,11 @@ use serde::de::{Deserialize, MapAccess, SeqAccess, Visitor}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::collections::BTreeMap; use std::fmt::Debug; -use std::marker::PhantomData; use std::{fmt, str}; use uuid::Uuid; use crate::annotated::Annotated; use crate::meta::Meta; -use crate::Getter; /// Alias for typed arrays. pub type Array = Vec>; From c9a00967462b0da0c7e5e79afc7e756d9c4bd014 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 11:13:01 +0200 Subject: [PATCH 04/16] Improve --- relay-event-schema/src/protocol/event.rs | 2 +- relay-protocol/src/condition.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 37210ff0b9..4c0ddbefef 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -5,7 +5,7 @@ use relay_common::time; #[cfg(feature = "jsonschema")] use relay_jsonschema_derive::JsonSchema; use relay_protocol::{ - Annotated, Arr, Array, Empty, FromValue, Getter, GetterIter, IntoValue, Object, Val, Value, + Annotated, Array, Empty, FromValue, Getter, GetterIter, IntoValue, Object, Val, Value, }; #[cfg(feature = "jsonschema")] use schemars::{gen::SchemaGenerator, schema::Schema}; diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index e089ca2f91..580cf16b31 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -838,7 +838,7 @@ impl std::ops::Not for RuleCondition { #[cfg(test)] mod tests { use super::*; - use crate::{Array, GetterIter}; + use crate::GetterIter; #[derive(Debug)] struct Exception { From 8b0a9e9d8bc956836be2e95647679531fcf7cb13 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 11:27:33 +0200 Subject: [PATCH 05/16] Improve --- relay-event-schema/src/protocol/event.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 4c0ddbefef..2bb6778450 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -810,7 +810,9 @@ impl Getter for Event { fn get_iter(&self, path: &str) -> Option> { Some(match path.strip_prefix("event.")? { - "exceptions" => GetterIter::new_annotated(self.exceptions.value()?.values.value()?), + "exceptions.values" => { + GetterIter::new_annotated(self.exceptions.value()?.values.value()?) + } _ => return None, }) } @@ -1264,6 +1266,14 @@ mod tests { Some(Val::String("route")), event.get_value("event.transaction.source") ); + + let mut exceptions = event.get_iter("event.exceptions.values").unwrap(); + let exception = exceptions.next().unwrap(); + assert_eq!( + Some(Val::String("canvas.contentDocument")), + exception.get_value("value") + ); + assert!(exceptions.next().is_none()); } #[test] From 47bf287ae3a44db05c0b3fb879dfd67e494436cc Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 11:43:00 +0200 Subject: [PATCH 06/16] Improve --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81816ddb9c..24a10d2c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - Fixes metrics dropped due to missing project state. ([#3553](https://github.com/getsentry/relay/issues/3553)) - Report outcomes for spans when transactions are rate limited. ([#3749](https://github.com/getsentry/relay/pull/3749)) +**Features**: + +- Add support for `all` and `any` `RuleCondition`(s). ([#3791](https://github.com/getsentry/relay/pull/3791)) + **Internal**: - Aggregate metrics before rate limiting. ([#3746](https://github.com/getsentry/relay/pull/3746)) From 0699bc801833d4c7e252885a40067d01f1002635 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:17:33 +0200 Subject: [PATCH 07/16] Improve --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a10d2c48..57afc906f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,6 @@ - Fixes metrics dropped due to missing project state. ([#3553](https://github.com/getsentry/relay/issues/3553)) - Report outcomes for spans when transactions are rate limited. ([#3749](https://github.com/getsentry/relay/pull/3749)) -**Features**: - -- Add support for `all` and `any` `RuleCondition`(s). ([#3791](https://github.com/getsentry/relay/pull/3791)) - **Internal**: - Aggregate metrics before rate limiting. ([#3746](https://github.com/getsentry/relay/pull/3746)) @@ -21,6 +17,7 @@ - Support extrapolation of metrics extracted from sampled data, as long as the sample rate is set in the DynamicSamplingContext. ([#3753](https://github.com/getsentry/relay/pull/3753)) - Extract thread ID and name in spans. ([#3771](https://github.com/getsentry/relay/pull/3771)) - Compute metrics summary on the extracted custom metrics. ([#3769](https://github.com/getsentry/relay/pull/3769)) +- Add support for `all` and `any` `RuleCondition`(s). ([#3791](https://github.com/getsentry/relay/pull/3791)) ## 24.6.0 From b8658eb4741a3a1c4012f0c651975c90a8373373 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:32:56 +0200 Subject: [PATCH 08/16] Update relay-protocol/src/condition.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index 580cf16b31..8a75a08837 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -737,7 +737,7 @@ impl RuleCondition { } } - /// Creates a [`AnyCondition`]. + /// Creates an [`AnyCondition`]. /// /// # Example /// From 82606727feb3436b4e9caeab039e8c673d7fafc4 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:33:05 +0200 Subject: [PATCH 09/16] Update relay-protocol/src/condition.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index 8a75a08837..0567222a8d 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -744,7 +744,7 @@ impl RuleCondition { /// ``` /// use relay_protocol::RuleCondition; /// - /// let condition = RuleCondition::for_any("event.exceptions", + /// let condition = RuleCondition::for_any("event.exception.values", /// RuleCondition::eq("name", "NullPointerException") /// ); /// ``` From bb6178f2ae44aec05e9037a9515bed002cc9f8a1 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:33:12 +0200 Subject: [PATCH 10/16] Update relay-protocol/src/condition.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/condition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index 0567222a8d..37c9cd5441 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -752,7 +752,7 @@ impl RuleCondition { Self::Any(AnyCondition::new(field, inner)) } - /// Creates a [`AllCondition`]. + /// Creates an [`AllCondition`]. /// /// # Example /// From 99b27793f6b8651e6f41070bbf5cfdcdef6b665b Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:33:30 +0200 Subject: [PATCH 11/16] Update relay-protocol/src/traits.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/traits.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index 299446cbf8..bed989ee35 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -125,8 +125,9 @@ pub trait IntoValue: Debug + Empty { } } -/// Hides an iterator of [`Getter`]s that is used to iterate over arbitrary [`Getter`] -/// implementations. +/// A type-erased iterator over a collection of [`Getter`]s. +/// +/// This type is usually returned from [`Getter::get_iter`]. /// /// # Example /// From 38f5c4f87c003933be57be67eec5876edd8d6beb Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:33:39 +0200 Subject: [PATCH 12/16] Update relay-protocol/src/traits.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index bed989ee35..2834dd8fb6 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -203,7 +203,7 @@ impl<'a> GetterIter<'a> { } } - /// Creates a new [`GetterIter`] given an element that can be converted into an iterator of + /// Creates a new [`GetterIter`] given a collection of /// [`Annotated`]s whose type implement [`Getter`]. pub fn new_annotated(iterator: I) -> Self where From fa67d18ce6861ef5da8a7fc3443e1561a0847205 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:33:45 +0200 Subject: [PATCH 13/16] Update relay-protocol/src/traits.rs Co-authored-by: Jan Michael Auer --- relay-protocol/src/traits.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index 2834dd8fb6..f353a89201 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -294,8 +294,9 @@ pub trait Getter { /// Returns the serialized value of a field pointed to by a `path`. fn get_value(&self, path: &str) -> Option>; - /// Returns a [`GetterIter`] that allows iteration on [`Getter`] implementations - /// of fields pointed on by `path`. + /// Returns an iterator over the array pointed to by a `path`. + /// + /// If the path does not exist or is not an array, this returns `None`. Note that `get_value` may not return a value for paths that expose an iterator. fn get_iter(&self, _path: &str) -> Option> { None } From 909e0ab50e74e7f7e5147a199ff3aa9fd6febf39 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:34:38 +0200 Subject: [PATCH 14/16] Improve --- relay-event-schema/src/protocol/event.rs | 4 ++-- relay-protocol/src/value.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 2bb6778450..e747ff736d 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -810,7 +810,7 @@ impl Getter for Event { fn get_iter(&self, path: &str) -> Option> { Some(match path.strip_prefix("event.")? { - "exceptions.values" => { + "exception.values" => { GetterIter::new_annotated(self.exceptions.value()?.values.value()?) } _ => return None, @@ -1267,7 +1267,7 @@ mod tests { event.get_value("event.transaction.source") ); - let mut exceptions = event.get_iter("event.exceptions.values").unwrap(); + let mut exceptions = event.get_iter("event.exception.values").unwrap(); let exception = exceptions.next().unwrap(); assert_eq!( Some(Val::String("canvas.contentDocument")), diff --git a/relay-protocol/src/value.rs b/relay-protocol/src/value.rs index e0fdf56566..2ae323326a 100644 --- a/relay-protocol/src/value.rs +++ b/relay-protocol/src/value.rs @@ -1,10 +1,11 @@ +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::{fmt, str}; + #[cfg(feature = "jsonschema")] use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::de::{Deserialize, MapAccess, SeqAccess, Visitor}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; -use std::collections::BTreeMap; -use std::fmt::Debug; -use std::{fmt, str}; use uuid::Uuid; use crate::annotated::Annotated; From 5fc314a5770ea7f6756796aa77612d3b7b4ce256 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:36:55 +0200 Subject: [PATCH 15/16] Improve --- relay-protocol/src/traits.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/relay-protocol/src/traits.rs b/relay-protocol/src/traits.rs index f353a89201..ce9dbbed40 100644 --- a/relay-protocol/src/traits.rs +++ b/relay-protocol/src/traits.rs @@ -127,35 +127,35 @@ pub trait IntoValue: Debug + Empty { /// A type-erased iterator over a collection of [`Getter`]s. /// -/// This type is usually returned from [`Getter::get_iter`]. +/// This type is usually returned from [`Getter::get_iter`]. /// /// # Example /// /// ``` /// use relay_protocol::{Getter, GetterIter, Val}; /// -/// struct Exception { -/// name: String, +/// struct Nested { +/// nested_value: String, /// } /// -/// impl Getter for Exception { +/// impl Getter for Nested { /// fn get_value(&self, path: &str) -> Option> { /// Some(match path { -/// "name" => self.name.as_str().into(), +/// "nested_value" => self.nested_value.as_str().into(), /// _ => return None, /// }) /// } /// } /// -/// struct Error { -/// platform: String, -/// exceptions: Vec, +/// struct Root { +/// value_1: String, +/// value_2: Vec, /// } /// -/// impl Getter for Error { +/// impl Getter for Root { /// fn get_value(&self, path: &str) -> Option> { -/// Some(match path.strip_prefix("error.")? { -/// "platform" => self.platform.as_str().into(), +/// Some(match path.strip_prefix("root.")? { +/// "value_1" => self.value_1.as_str().into(), /// _ => { /// return None; /// } @@ -165,8 +165,8 @@ pub trait IntoValue: Debug + Empty { /// // `get_iter` should return a `GetterIter` that can be used for iterating on the /// // `Getter`(s). /// fn get_iter(&self, path: &str) -> Option> { -/// Some(match path.strip_prefix("error.")? { -/// "exceptions" => GetterIter::new(self.exceptions.iter()), +/// Some(match path.strip_prefix("root.")? { +/// "value_2" => GetterIter::new(self.value_2.iter()), /// _ => return None, /// }) /// } @@ -176,12 +176,12 @@ pub trait IntoValue: Debug + Empty { /// fn matches(instance: &T) -> bool /// where T: Getter + ?Sized /// { -/// let Some(mut getter_iter) = instance.get_iter("error.exceptions") else { +/// let Some(mut getter_iter) = instance.get_iter("root.value_2") else { /// return false; /// }; /// /// for getter in getter_iter { -/// let exception_name = getter.get_value("name"); +/// let nested_value = getter.get_value("nested_value"); /// } /// /// true From d35ba6a8acc8bf617bc75d39f62f3e83ce2f91b1 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Fri, 5 Jul 2024 13:38:19 +0200 Subject: [PATCH 16/16] Improve --- relay-protocol/src/condition.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/relay-protocol/src/condition.rs b/relay-protocol/src/condition.rs index 37c9cd5441..4d8d40e893 100644 --- a/relay-protocol/src/condition.rs +++ b/relay-protocol/src/condition.rs @@ -525,7 +525,7 @@ pub enum RuleCondition { /// ``` /// use relay_protocol::RuleCondition; /// - /// let condition = RuleCondition::for_any("event.exceptions", + /// let condition = RuleCondition::for_any("obj.exceptions", /// RuleCondition::eq("name", "NullPointerException") /// ); /// ``` @@ -538,7 +538,7 @@ pub enum RuleCondition { /// ``` /// use relay_protocol::RuleCondition; /// - /// let condition = RuleCondition::for_all("event.exceptions", + /// let condition = RuleCondition::for_all("obj.exceptions", /// RuleCondition::eq("name", "NullPointerException") /// ); /// ``` @@ -744,7 +744,7 @@ impl RuleCondition { /// ``` /// use relay_protocol::RuleCondition; /// - /// let condition = RuleCondition::for_any("event.exception.values", + /// let condition = RuleCondition::for_any("obj.exceptions", /// RuleCondition::eq("name", "NullPointerException") /// ); /// ``` @@ -759,7 +759,7 @@ impl RuleCondition { /// ``` /// use relay_protocol::RuleCondition; /// - /// let condition = RuleCondition::for_all("event.exceptions", + /// let condition = RuleCondition::for_all("obj.exceptions", /// RuleCondition::eq("name", "NullPointerException") /// ); /// ``` @@ -947,7 +947,7 @@ mod tests { }, { "op": "any", - "name": "event.exceptions", + "name": "obj.exceptions", "inner": { "op": "glob", "name": "value", @@ -956,7 +956,7 @@ mod tests { }, { "op": "all", - "name": "event.exceptions", + "name": "obj.exceptions", "inner": { "op": "glob", "name": "value", @@ -1033,7 +1033,7 @@ mod tests { ), AnyCondition( op: "any", - name: "event.exceptions", + name: "obj.exceptions", inner: GlobCondition( op: "glob", name: "value", @@ -1044,7 +1044,7 @@ mod tests { ), AllCondition( op: "all", - name: "event.exceptions", + name: "obj.exceptions", inner: GlobCondition( op: "glob", name: "value",