diff --git a/relay-event-normalization/src/eap/trimming.rs b/relay-event-normalization/src/eap/trimming.rs index b5eac1b8e3..58bde9d05c 100644 --- a/relay-event-normalization/src/eap/trimming.rs +++ b/relay-event-normalization/src/eap/trimming.rs @@ -716,8 +716,7 @@ mod tests { // That leaves 9B for the string's value. // Note that the `number` field doesn't take up any size. // The `"footer"` is removed because it comes after the attributes and there's no space left. - let state = - ProcessingState::new_root(Some(Cow::Owned(FieldAttrs::default().max_bytes(50))), []); + let state = ProcessingState::root_builder().max_bytes(50).build(); processor::process_value(&mut value, &mut processor, &state).unwrap(); insta::assert_json_snapshot!(SerializableAnnotated(&value), @r###" @@ -883,8 +882,7 @@ mod tests { }); let mut processor = TrimmingProcessor::new(100); - let state = - ProcessingState::new_root(Some(Cow::Owned(FieldAttrs::default().max_bytes(30))), []); + let state = ProcessingState::root_builder().max_bytes(30).build(); processor::process_value(&mut value, &mut processor, &state).unwrap(); insta::assert_json_snapshot!(SerializableAnnotated(&value), @r###" @@ -937,8 +935,7 @@ mod tests { let mut attributes = Annotated::new(attributes); - let state = - ProcessingState::new_root(Some(Cow::Owned(FieldAttrs::default().max_bytes(40))), []); + let state = ProcessingState::root_builder().max_bytes(40).build(); processor::process_value(&mut attributes, &mut TrimmingProcessor::new(20), &state).unwrap(); let attributes_after_trimming = attributes.clone(); processor::process_value(&mut attributes, &mut TrimmingProcessor::new(20), &state).unwrap(); diff --git a/relay-event-schema/src/processor/attrs.rs b/relay-event-schema/src/processor/attrs.rs index 7b55aa4f6d..fec2145a20 100644 --- a/relay-event-schema/src/processor/attrs.rs +++ b/relay-event-schema/src/processor/attrs.rs @@ -211,7 +211,7 @@ impl FieldAttrs { self } - /// Sets whether this field can have an empty value. + /// Sets whether this field's value must be nonempty. /// /// This is distinct from `required`. An empty string (`""`) passes the "required" check but not the /// "nonempty" one. @@ -239,8 +239,8 @@ impl FieldAttrs { } /// Sets the maximum number of characters allowed in the field. - pub const fn max_chars(mut self, max_chars: usize) -> Self { - self.max_chars = SizeMode::Static(Some(max_chars)); + pub const fn max_chars(mut self, max_chars: Option) -> Self { + self.max_chars = SizeMode::Static(max_chars); self } @@ -254,8 +254,8 @@ impl FieldAttrs { } /// Sets the maximum number of bytes allowed in the field. - pub const fn max_bytes(mut self, max_bytes: usize) -> Self { - self.max_bytes = SizeMode::Static(Some(max_bytes)); + pub const fn max_bytes(mut self, max_bytes: Option) -> Self { + self.max_bytes = SizeMode::Static(max_bytes); self } @@ -355,6 +355,96 @@ impl Deref for BoxCow<'_, T> { } } +/// A builder for root [`ProcessingStates`](ProcessingState). +/// +/// This is created by [`ProcessingState::root_builder`]. +#[derive(Debug, Clone)] +pub struct ProcessingStateBuilder { + attrs: Option, + value_type: EnumSet, +} + +impl ProcessingStateBuilder { + /// Modifies the attributes of the root field. + pub fn attrs FieldAttrs>(mut self, f: F) -> Self { + let attrs = self.attrs.take().unwrap_or_default(); + self.attrs = Some(f(attrs)); + self + } + + /// Sets whether a value in the root field is required. + pub fn required(self, required: bool) -> Self { + self.attrs(|attrs| attrs.required(required)) + } + + /// Sets whether the root field's value must be nonempty. + /// + /// This is distinct from `required`. An empty string (`""`) passes the "required" check but not the + /// "nonempty" one. + pub fn nonempty(self, nonempty: bool) -> Self { + self.attrs(|attrs| attrs.nonempty(nonempty)) + } + + /// Sets whether whitespace should be trimmed on the root field before validation. + pub fn trim_whitespace(self, trim_whitespace: bool) -> Self { + self.attrs(|attrs| attrs.trim_whitespace(trim_whitespace)) + } + + /// Sets whether the root field contains PII. + pub fn pii(self, pii: Pii) -> Self { + self.attrs(|attrs| attrs.pii(pii)) + } + + /// Sets whether the root field contains PII dynamically based on the current state. + pub fn pii_dynamic(self, pii: fn(&ProcessingState) -> Pii) -> Self { + self.attrs(|attrs| attrs.pii_dynamic(pii)) + } + + /// Sets the maximum number of chars allowed in the root field. + pub fn max_chars(self, max_chars: impl Into>) -> Self { + self.attrs(|attrs| attrs.max_chars(max_chars.into())) + } + + /// Sets the maximum number of characters allowed in the root field dynamically based on the current state. + pub fn max_chars_dynamic(self, max_chars: fn(&ProcessingState) -> Option) -> Self { + self.attrs(|attrs| attrs.max_chars_dynamic(max_chars)) + } + + /// Sets the maximum number of bytes allowed in the root field. + pub fn max_bytes(self, max_bytes: impl Into>) -> Self { + self.attrs(|attrs| attrs.max_bytes(max_bytes.into())) + } + + /// Sets the maximum number of bytes allowed in the root field dynamically based on the current state. + pub fn max_bytes_dynamic(self, max_bytes: fn(&ProcessingState) -> Option) -> Self { + self.attrs(|attrs| attrs.max_bytes_dynamic(max_bytes)) + } + + /// Sets whether additional properties should be retained during normalization. + pub fn retain(self, retain: bool) -> Self { + self.attrs(|attrs| attrs.retain(retain)) + } + + /// Sets the value type for the root state. + pub fn value_type(mut self, value_type: EnumSet) -> Self { + self.value_type = value_type; + self + } + + /// Consumes the builder and returns a root [`ProcessingState`] with + /// the configured attributes and value type. + pub fn build(self) -> ProcessingState<'static> { + let Self { attrs, value_type } = self; + ProcessingState { + parent: None, + path_item: None, + attrs: attrs.map(Cow::Owned), + value_type, + depth: 0, + } + } +} + /// An event's processing state. /// /// The processing state describes an item in an event which is being processed, an example @@ -405,6 +495,25 @@ impl<'a> ProcessingState<'a> { } } + /// Creates a builder that can be used to easily create + /// a custom root state. + /// + /// # Example + /// ``` + /// use relay_event_schema::processor::ProcessingState; + /// + /// let root = ProcessingState::root_builder() + /// .max_bytes(50) + /// .retain(true) + /// .build(); + /// ``` + pub fn root_builder() -> ProcessingStateBuilder { + ProcessingStateBuilder { + attrs: None, + value_type: EnumSet::empty(), + } + } + /// Derives a processing state by entering a borrowed key. pub fn enter_borrowed( &'a self,