From 61e33925263912e133c694aa9150639ac8e8f422 Mon Sep 17 00:00:00 2001 From: David Herberth Date: Tue, 29 Sep 2026 14:44:07 +0200 Subject: [PATCH] ref(project-config): Use Box<[_]> instead of Vec<_> --- relay-dynamic-config/src/defaults.rs | 7 +- relay-dynamic-config/src/global.rs | 8 +- relay-dynamic-config/src/metrics.rs | 67 +++++++++------ relay-dynamic-config/src/project.rs | 82 ++++++++++++++----- relay-event-normalization/src/event.rs | 2 +- .../src/normalize/breakdowns.rs | 4 +- .../src/normalize/mod.rs | 24 +++--- .../src/transactions/processor.rs | 4 +- relay-filter/src/config.rs | 8 +- relay-filter/src/csp.rs | 16 ++-- relay-pii/src/builtin.rs | 26 ++++-- relay-pii/src/config.rs | 4 +- relay-pii/src/convert.rs | 69 +++++++++------- relay-pii/src/eap.rs | 10 ++- relay-pii/src/legacy.rs | 8 +- relay-sampling/src/config.rs | 15 ++-- relay-server/src/metrics/rate_limits.rs | 4 +- relay-server/src/processing/spans/process.rs | 7 +- .../src/processing/utils/dynamic_sampling.rs | 9 +- relay-server/src/processing/utils/event.rs | 4 +- relay-server/src/services/processor.rs | 2 +- .../src/services/projects/project/info.rs | 2 +- 22 files changed, 238 insertions(+), 144 deletions(-) diff --git a/relay-dynamic-config/src/defaults.rs b/relay-dynamic-config/src/defaults.rs index 746ef78fde8..f3c7fee0bd8 100644 --- a/relay-dynamic-config/src/defaults.rs +++ b/relay-dynamic-config/src/defaults.rs @@ -22,7 +22,8 @@ pub fn add_span_metrics(project_config: &mut ProjectConfig) { // The metric is always tagged with `is_segment` (`true`/`false`) and additionally // segment spans are gain the additional tag `was_transaction` if the segment span was created // from a transaction. - config.metrics.push(MetricSpec { + let mut metrics = std::mem::take(&mut config.metrics).into_vec(); + metrics.push(MetricSpec { category: DataCategory::Span, mri: "c:spans/usage@none".into(), field: None, @@ -38,8 +39,10 @@ pub fn add_span_metrics(project_config: &mut ProjectConfig) { ), // Fallback, for all non segment spans. Tag::with_key("is_segment").with_value("false").always(), - ], + ] + .into_boxed_slice(), }); + config.metrics = metrics.into_boxed_slice(); if config.version == 0 { config.version = MetricExtractionConfig::MAX_SUPPORTED_VERSION; diff --git a/relay-dynamic-config/src/global.rs b/relay-dynamic-config/src/global.rs index 4c93f396ca1..e9c3a62ec09 100644 --- a/relay-dynamic-config/src/global.rs +++ b/relay-dynamic-config/src/global.rs @@ -23,8 +23,8 @@ pub struct GlobalConfig { #[serde(skip_serializing_if = "Option::is_none")] pub measurements: Option, /// Quotas that apply to all projects. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub quotas: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub quotas: Box<[Quota]>, /// Configuration for global inbound filters. /// /// These filters are merged with generic filters in project configs before @@ -128,9 +128,9 @@ pub struct Options { #[serde( rename = "relay.span-normalization.allowed_hosts", deserialize_with = "default_on_error", - skip_serializing_if = "Vec::is_empty" + skip_serializing_if = "<[_]>::is_empty" )] - pub http_span_allowed_hosts: Vec, + pub http_span_allowed_hosts: Box<[String]>, /// Instructs relay to store attachments in objectstore instead of sending chunks via kafka. /// diff --git a/relay-dynamic-config/src/metrics.rs b/relay-dynamic-config/src/metrics.rs index ad1ea122ff6..2a4b85b90a0 100644 --- a/relay-dynamic-config/src/metrics.rs +++ b/relay-dynamic-config/src/metrics.rs @@ -76,14 +76,14 @@ pub struct CustomMeasurementConfig { #[derive(Debug, Clone, Copy)] pub struct CombinedMetricExtractionConfig<'a> { global: &'a MetricExtractionGroups, - project: &'a MetricExtractionConfig, + project: Option<&'a MetricExtractionConfig>, } impl<'a> CombinedMetricExtractionConfig<'a> { /// Empty config, used in tests and as a fallback. pub const EMPTY: Self = Self { global: MetricExtractionGroups::EMPTY, - project: &MetricExtractionConfig::empty(), + project: None, }; /// Creates a new combined view from two references. @@ -96,12 +96,18 @@ impl<'a> CombinedMetricExtractionConfig<'a> { } } - Self { global, project } + Self { + global, + project: Some(project), + } } /// Returns an iterator of metric specs. pub fn metrics(&self) -> impl Iterator { - let project = self.project.metrics.iter(); + let project = self + .project + .into_iter() + .flat_map(|config| config.metrics.iter()); let enabled_global = self .enabled_groups() .flat_map(|template| template.metrics.iter()); @@ -111,7 +117,10 @@ impl<'a> CombinedMetricExtractionConfig<'a> { /// Returns an iterator of tag mappings. pub fn tags(&self) -> impl Iterator { - let project = self.project.tags.iter(); + let project = self + .project + .into_iter() + .flat_map(|config| config.tags.iter()); let enabled_global = self .enabled_groups() .flat_map(|template| template.tags.iter()); @@ -121,7 +130,10 @@ impl<'a> CombinedMetricExtractionConfig<'a> { fn enabled_groups(&self) -> impl Iterator { self.global.groups.iter().filter_map(|(key, template)| { - let is_enabled_by_override = self.project.global_groups.get(key).map(|c| c.is_enabled); + let is_enabled_by_override = self + .project + .and_then(|project| project.global_groups.get(key)) + .map(|config| config.is_enabled); let is_enabled = is_enabled_by_override.unwrap_or(template.is_enabled); is_enabled.then_some(template) @@ -169,15 +181,15 @@ pub struct MetricExtractionGroup { pub is_enabled: bool, /// A list of metric specifications to extract. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub metrics: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub metrics: Box<[MetricSpec]>, /// A list of tags to add to previously extracted metrics. /// /// These tags add further tags to a range of metrics. If some metrics already have a matching /// tag extracted, the existing tag is left unchanged. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub tags: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub tags: Box<[TagMapping]>, } /// Configuration for generic extraction of metrics from all data categories. @@ -195,15 +207,15 @@ pub struct MetricExtractionConfig { pub global_groups: BTreeMap, /// A list of metric specifications to extract. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub metrics: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub metrics: Box<[MetricSpec]>, /// A list of tags to add to previously extracted metrics. /// /// These tags add further tags to a range of metrics. If some metrics already have a matching /// tag extracted, the existing tag is left unchanged. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub tags: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub tags: Box<[TagMapping]>, /// This config has been extended with fields from `conditional_tagging`. /// @@ -236,12 +248,12 @@ impl MetricExtractionConfig { /// Returns an empty `MetricExtractionConfig` with the latest version. /// /// As opposed to `default()`, this will be enabled once populated with specs. - pub const fn empty() -> Self { + pub fn empty() -> Self { Self { version: Self::MAX_SUPPORTED_VERSION, global_groups: BTreeMap::new(), - metrics: Vec::new(), - tags: Vec::new(), + metrics: Box::new([]), + tags: Box::new([]), _conditional_tags_extended: false, _span_metrics_extended: false, } @@ -356,8 +368,8 @@ pub struct MetricSpec { /// Tags can be conditional, see [`TagSpec`] for configuration options. For this reason, it is /// possible to list tag keys multiple times, each with different conditions. The first matching /// condition will be applied. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub tags: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub tags: Box<[TagSpec]>, } /// Mapping between extracted metrics and additional tags to extract. @@ -368,7 +380,7 @@ pub struct TagMapping { /// /// Entries in this list can contain wildcards to match metrics with dynamic MRIs. #[serde(default)] - pub metrics: Vec, + pub metrics: Box<[LazyGlob]>, /// A list of tags to add to the metric. /// @@ -376,7 +388,7 @@ pub struct TagMapping { /// possible to list tag keys multiple times, each with different conditions. The first matching /// condition will be applied. #[serde(default)] - pub tags: Vec, + pub tags: Box<[TagSpec]>, } impl TagMapping { @@ -530,10 +542,12 @@ pub fn convert_conditional_tagging(project_config: &mut ProjectConfig) { return; } - config.tags.extend(TaggingRuleConverter { + let mut tags = std::mem::take(&mut config.tags).into_vec(); + tags.extend(TaggingRuleConverter { rules: rules.iter().cloned().peekable(), tags: Vec::new(), }); + config.tags = tags.into_boxed_slice(); config._conditional_tags_extended = true; if config.version == 0 { @@ -571,7 +585,7 @@ where return Some(TagMapping { metrics: old.target_metrics.into_iter().map(LazyGlob::new).collect(), - tags: std::mem::take(&mut self.tags), + tags: std::mem::take(&mut self.tags).into_boxed_slice(), }); } } @@ -582,6 +596,13 @@ mod tests { use super::*; use similar_asserts::assert_eq; + #[test] + fn empty_combined_config() { + let combined = CombinedMetricExtractionConfig::EMPTY; + assert_eq!(combined.metrics().count(), 0); + assert_eq!(combined.tags().count(), 0); + } + #[test] fn parse_tag_spec_value() { let json = r#"{"key":"foo","value":"bar"}"#; diff --git a/relay-dynamic-config/src/project.rs b/relay-dynamic-config/src/project.rs index 1233f367c7e..1855156300b 100644 --- a/relay-dynamic-config/src/project.rs +++ b/relay-dynamic-config/src/project.rs @@ -21,9 +21,9 @@ use crate::{GRADUATED_FEATURE_FLAGS, defaults}; #[serde(default, rename_all = "camelCase")] pub struct ProjectConfig { /// URLs that are permitted for cross original JavaScript requests. - pub allowed_domains: Vec, + pub allowed_domains: Box<[String]>, /// List of relay public keys that are permitted to access this project. - pub trusted_relays: Vec, + pub trusted_relays: Box<[PublicKey]>, /// Configuration for trusted Relay behaviour. #[serde(skip_serializing_if = "TrustedRelayConfig::is_empty")] pub trusted_relay_settings: TrustedRelayConfig, @@ -51,8 +51,8 @@ pub struct ProjectConfig { #[serde(default, skip_serializing_if = "TrimmingConfigs::is_empty")] pub trimming: TrimmingConfigs, /// Usage quotas for this project. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub quotas: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub quotas: Box<[Quota]>, /// Configuration for sampling traces, if not present there will be no sampling. #[serde(alias = "dynamicSampling", skip_serializing_if = "Option::is_none")] pub sampling: Option>, @@ -73,14 +73,14 @@ pub struct ProjectConfig { #[serde(default, skip_serializing_if = "skip_metrics_extraction")] pub metric_extraction: ErrorBoundary, /// Rules for applying metrics tags depending on the event's content. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub metric_conditional_tagging: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub metric_conditional_tagging: Box<[TaggingRule]>, /// Exposable features enabled for this project. #[serde(skip_serializing_if = "FeatureSet::is_empty")] pub features: FeatureSet, /// Transaction renaming rules. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub tx_name_rules: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub tx_name_rules: Box<[TransactionNameRule]>, /// Whether or not a project is ready to mark all URL transactions as "sanitized". #[serde(skip_serializing_if = "is_false")] pub tx_name_ready: bool, @@ -89,7 +89,7 @@ pub struct ProjectConfig { /// These are currently not used by Relay, and only here to be forwarded to old /// relays that might still need them. #[serde(skip_serializing_if = "Option::is_none")] - pub span_description_rules: Option>, + pub span_description_rules: Option>, } impl ProjectConfig { @@ -110,7 +110,9 @@ impl ProjectConfig { } fn remove_invalid_quotas(&mut self, report_errors: bool) { - let invalid_quotas: Vec<_> = self.quotas.extract_if(.., |q| !q.is_valid()).collect(); + let mut quotas = std::mem::take(&mut self.quotas).into_vec(); + let invalid_quotas: Vec<_> = quotas.extract_if(.., |q| !q.is_valid()).collect(); + self.quotas = quotas.into_boxed_slice(); if report_errors { if !invalid_quotas.is_empty() { { @@ -143,8 +145,8 @@ impl ProjectConfig { impl Default for ProjectConfig { fn default() -> Self { ProjectConfig { - allowed_domains: vec!["*".to_owned()], - trusted_relays: vec![], + allowed_domains: vec!["*".to_owned()].into_boxed_slice(), + trusted_relays: Box::new([]), trusted_relay_settings: TrustedRelayConfig::default(), pii_config: None, grouping_config: None, @@ -154,16 +156,16 @@ impl Default for ProjectConfig { downsampled_event_retention: None, retentions: Default::default(), trimming: Default::default(), - quotas: Vec::new(), + quotas: Box::new([]), sampling: None, measurements: None, breakdowns_v2: None, performance_score: Default::default(), session_metrics: SessionMetricsConfig::default(), metric_extraction: Default::default(), - metric_conditional_tagging: Vec::new(), + metric_conditional_tagging: Box::new([]), features: Default::default(), - tx_name_rules: Vec::new(), + tx_name_rules: Box::new([]), tx_name_ready: false, span_description_rules: None, } @@ -184,8 +186,8 @@ fn skip_metrics_extraction(boundary: &ErrorBoundary) -> #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase", remote = "ProjectConfig")] pub struct LimitedProjectConfig { - pub allowed_domains: Vec, - pub trusted_relays: Vec, + pub allowed_domains: Box<[String]>, + pub trusted_relays: Box<[PublicKey]>, pub pii_config: Option, #[serde(skip_serializing_if = "ProjectFiltersConfig::is_empty")] pub filter_settings: ProjectFiltersConfig, @@ -195,8 +197,8 @@ pub struct LimitedProjectConfig { pub trimming: TrimmingConfigs, #[serde(skip_serializing_if = "SessionMetricsConfig::is_disabled")] pub session_metrics: SessionMetricsConfig, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub metric_conditional_tagging: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub metric_conditional_tagging: Box<[TaggingRule]>, #[serde(skip_serializing_if = "Option::is_none")] pub measurements: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -205,8 +207,8 @@ pub struct LimitedProjectConfig { pub performance_score: Option, #[serde(skip_serializing_if = "FeatureSet::is_empty")] pub features: FeatureSet, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub tx_name_rules: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + pub tx_name_rules: Box<[TransactionNameRule]>, /// Whether or not a project is ready to mark all URL transactions as "sanitized". #[serde(skip_serializing_if = "is_false")] pub tx_name_ready: bool, @@ -215,7 +217,7 @@ pub struct LimitedProjectConfig { /// These are currently not used by Relay, and only here to be forwarded to old /// relays that might still need them. #[serde(skip_serializing_if = "Option::is_none")] - pub span_description_rules: Option>, + pub span_description_rules: Option>, } /// Per-Category settings for retention policy. @@ -304,4 +306,40 @@ mod tests { assert!(project_config.features.has(*feature)); } } + + #[test] + fn sanitize_removes_invalid_quotas() { + let mut config: ProjectConfig = + serde_json::from_str(r#"{"quotas":[{"limit":0},{"limit":1},{"limit":0}]}"#).unwrap(); + + config.sanitize(false); + + assert_eq!(config.quotas.len(), 2); + assert!(config.quotas.iter().all(Quota::is_valid)); + } + + #[test] + fn sanitize_extends_metric_extraction() { + let mut config: ProjectConfig = serde_json::from_value(serde_json::json!({ + "metricConditionalTagging": [{ + "condition": {"op": "and", "inner": []}, + "targetMetrics": ["c:spans/custom@none"], + "targetTag": "key", + "tagValue": "value" + }], + "metricExtraction": { + "version": MetricExtractionConfig::MAX_SUPPORTED_VERSION, + "metrics": [{"category": "span", "mri": "c:spans/custom@none"}] + } + })) + .unwrap(); + + config.sanitize(false); + + let extraction = config.metric_extraction.ok().unwrap(); + assert_eq!(extraction.metrics.len(), 2); + assert_eq!(extraction.metrics[1].mri, "c:spans/usage@none"); + assert_eq!(extraction.tags.len(), 1); + assert_eq!(extraction.tags[0].tags[0].key, "key"); + } } diff --git a/relay-event-normalization/src/event.rs b/relay-event-normalization/src/event.rs index 0a039ae269a..eaabfdc197d 100644 --- a/relay-event-normalization/src/event.rs +++ b/relay-event-normalization/src/event.rs @@ -136,7 +136,7 @@ pub struct NormalizationConfig<'a> { /// Configuration for replacing identifiers in the span description with placeholders. /// /// This is similar to `transaction_name_config`, but applies to span descriptions. - pub span_description_rules: Option<&'a Vec>, + pub span_description_rules: Option<&'a [SpanDescriptionRule]>, /// Configuration for generating performance score measurements for web vitals. pub performance_score: Option<&'a PerformanceScoreConfig>, diff --git a/relay-event-normalization/src/normalize/breakdowns.rs b/relay-event-normalization/src/normalize/breakdowns.rs index e88431693ab..57186be590f 100644 --- a/relay-event-normalization/src/normalize/breakdowns.rs +++ b/relay-event-normalization/src/normalize/breakdowns.rs @@ -80,7 +80,7 @@ pub struct SpanOperationsConfig { /// The match is successful if the span operation name starts with any string in the array. If /// any string in the array has at least one match, then a breakdown group is created, and its /// name will be the matched string. - pub matches: Vec, + pub matches: Box<[String]>, } impl EmitBreakdowns for SpanOperationsConfig { @@ -348,7 +348,7 @@ mod tests { let mut config = HashMap::new(); let span_ops_config = BreakdownConfig::SpanOperations(SpanOperationsConfig { - matches: vec!["http".to_owned(), "db".to_owned()], + matches: vec!["http".to_owned(), "db".to_owned()].into_boxed_slice(), }); config.insert("span_ops".to_owned(), span_ops_config.clone()); diff --git a/relay-event-normalization/src/normalize/mod.rs b/relay-event-normalization/src/normalize/mod.rs index d366ca647c5..3fc64221030 100644 --- a/relay-event-normalization/src/normalize/mod.rs +++ b/relay-event-normalization/src/normalize/mod.rs @@ -61,8 +61,8 @@ impl BuiltinMeasurementKey { #[serde(default, rename_all = "camelCase")] pub struct MeasurementsConfig { /// A list of measurements that are built-in and are not subject to custom measurement limits. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub builtin_measurements: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub builtin_measurements: Box<[BuiltinMeasurementKey]>, /// The maximum number of measurements allowed per event that are not known measurements. pub max_custom_measurements: usize, @@ -107,12 +107,12 @@ impl<'a> CombinedMeasurementsConfig<'a> { ) -> impl Iterator + 'a { let project = self .project - .map(|p| p.builtin_measurements.as_slice()) + .map(|p| p.builtin_measurements.as_ref()) .unwrap_or_default(); let global = self .global - .map(|g| g.builtin_measurements.as_slice()) + .map(|g| g.builtin_measurements.as_ref()) .unwrap_or_default(); project @@ -166,8 +166,8 @@ pub struct PerformanceScoreProfile { /// Name of the profile, used for debugging and faceting multiple profiles pub name: Option, /// Score components - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub score_components: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub score_components: Box<[PerformanceScoreWeightedComponent]>, /// See [`RuleCondition`] for all available options to specify and combine conditions. pub condition: Option, /// The version of the profile, used to isolate changes to score calculations. @@ -182,8 +182,8 @@ pub struct PerformanceScoreProfile { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PerformanceScoreConfig { /// List of performance profiles, only the first with matching conditions will be applied. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub profiles: Vec, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub profiles: Box<[PerformanceScoreProfile]>, } /// Regex that matches version and/or date patterns at the end of a model name. @@ -442,13 +442,13 @@ mod tests { let baz = BuiltinMeasurementKey::new("baz", MetricUnit::Duration(DurationUnit::Week)); let proj = MeasurementsConfig { - builtin_measurements: vec![foo.clone(), bar.clone()], + builtin_measurements: vec![foo.clone(), bar.clone()].into_boxed_slice(), max_custom_measurements: 4, }; let glob = MeasurementsConfig { // The 'bar' here will be ignored since it's a duplicate from the project level. - builtin_measurements: vec![baz.clone(), bar.clone()], + builtin_measurements: vec![baz.clone(), bar.clone()].into_boxed_slice(), max_custom_measurements: 4, }; let dynamic_config = CombinedMeasurementsConfig::new(Some(&proj), Some(&glob)); @@ -465,12 +465,12 @@ mod tests { assert!(dynamic_config.max_custom_measurements().is_none()); let proj = MeasurementsConfig { - builtin_measurements: vec![], + builtin_measurements: Box::new([]), max_custom_measurements: 3, }; let glob = MeasurementsConfig { - builtin_measurements: vec![], + builtin_measurements: Box::new([]), max_custom_measurements: 4, }; diff --git a/relay-event-normalization/src/transactions/processor.rs b/relay-event-normalization/src/transactions/processor.rs index 07ba15b1321..70b48d34908 100644 --- a/relay-event-normalization/src/transactions/processor.rs +++ b/relay-event-normalization/src/transactions/processor.rs @@ -214,14 +214,14 @@ impl Processor for TransactionsProcessor<'_> { #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] pub struct SpanOpDefaults { /// List of rules to apply. First match wins. - pub rules: Vec, + pub rules: Box<[SpanOpDefaultRule]>, } impl SpanOpDefaults { /// Gets a borrowed version of this config. pub fn borrow(&self) -> BorrowedSpanOpDefaults<'_> { BorrowedSpanOpDefaults { - rules: self.rules.as_slice(), + rules: self.rules.as_ref(), } } } diff --git a/relay-filter/src/config.rs b/relay-filter/src/config.rs index 2e591d8fc77..d955cb287c4 100644 --- a/relay-filter/src/config.rs +++ b/relay-filter/src/config.rs @@ -144,7 +144,7 @@ impl Serialize for LegacyBrowser { #[serde(rename_all = "camelCase")] pub struct ClientIpsFilterConfig { /// Blacklisted client ip addresses. - pub blacklisted_ips: Vec, + pub blacklisted_ips: Box<[String]>, } impl ClientIpsFilterConfig { @@ -159,7 +159,7 @@ impl ClientIpsFilterConfig { #[serde(rename_all = "camelCase")] pub struct CspFilterConfig { /// Disallowed sources for CSP reports. - pub disallowed_sources: Vec, + pub disallowed_sources: Box<[String]>, } impl CspFilterConfig { @@ -625,11 +625,11 @@ mod tests { let filters_config = ProjectFiltersConfig { browser_extensions: FilterConfig { is_enabled: true }, client_ips: ClientIpsFilterConfig { - blacklisted_ips: vec!["127.0.0.1".to_owned()], + blacklisted_ips: vec!["127.0.0.1".to_owned()].into_boxed_slice(), }, web_crawlers: FilterConfig { is_enabled: true }, csp: CspFilterConfig { - disallowed_sources: vec!["https://*".to_owned()], + disallowed_sources: vec!["https://*".to_owned()].into_boxed_slice(), }, error_messages: ErrorMessagesFilterConfig { patterns: TypedPatterns::from(["Panic".to_owned()]), diff --git a/relay-filter/src/csp.rs b/relay-filter/src/csp.rs index 19f467c6977..ba81a3e7ff9 100644 --- a/relay-filter/src/csp.rs +++ b/relay-filter/src/csp.rs @@ -399,7 +399,7 @@ mod tests { fn test_filters_known_blocked_source_files() { let event = get_csp_event(None, Some("http://known.bad.com"), None); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -414,7 +414,7 @@ mod tests { fn test_does_not_filter_benign_source_files() { let event = get_csp_event(None, Some("http://good.file.com"), None); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -429,7 +429,7 @@ mod tests { fn test_filters_known_document_uris() { let event = get_csp_event(None, None, Some("http://known.bad.com")); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -444,7 +444,7 @@ mod tests { fn test_filters_known_blocked_uris() { let event = get_csp_event(Some("http://known.bad.com"), None, None); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -459,7 +459,7 @@ mod tests { fn test_does_not_filter_benign_uris() { let event = get_csp_event(Some("http://good.file.com"), None, None); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -475,7 +475,7 @@ mod tests { let mut event = get_csp_event(Some("http://known.bad.com"), None, None); event.ty = Annotated::from(EventType::Transaction); let config = CspFilterConfig { - disallowed_sources: vec!["http://known.bad.com".to_owned()], + disallowed_sources: vec!["http://known.bad.com".to_owned()].into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -515,7 +515,7 @@ mod tests { for (blocked_uri, source_file) in examples { let event = get_csp_event(*blocked_uri, *source_file, None); let config = CspFilterConfig { - disallowed_sources: get_disallowed_sources(), + disallowed_sources: get_disallowed_sources().into_boxed_slice(), }; let actual = should_filter(&event, &config); @@ -538,7 +538,7 @@ mod tests { for (blocked_uri, source_file) in examples { let event = get_csp_event(*blocked_uri, *source_file, None); let config = CspFilterConfig { - disallowed_sources: get_disallowed_sources(), + disallowed_sources: get_disallowed_sources().into_boxed_slice(), }; let actual = should_filter(&event, &config); diff --git a/relay-pii/src/builtin.rs b/relay-pii/src/builtin.rs index f9fce748756..610e7a1cb79 100644 --- a/relay-pii/src/builtin.rs +++ b/relay-pii/src/builtin.rs @@ -45,7 +45,8 @@ declare_builtin_rules! { "@bearer".into(), "@password".into(), "@usssn".into(), - ], + ] + .into_boxed_slice(), hide_inner: false, }), redaction: Redaction::Default, @@ -62,7 +63,8 @@ declare_builtin_rules! { "@bearer:filter".into(), "@password:filter".into(), "@usssn:filter".into(), - ], + ] + .into_boxed_slice(), hide_inner: false, }), redaction: Redaction::Default, @@ -456,7 +458,10 @@ mod tests { let config = PiiConfig { applications: { let mut map = BTreeMap::new(); - map.insert(ValueType::String.into(), vec![$rule.to_string()]); + map.insert( + ValueType::String.into(), + vec![$rule.to_string()].into_boxed_slice(), + ); map }, ..PiiConfig::default() @@ -477,7 +482,10 @@ mod tests { let config = PiiConfig { applications: { let mut map = BTreeMap::new(); - map.insert(ValueType::String.into(), vec![$rule.to_string()]); + map.insert( + ValueType::String.into(), + vec![$rule.to_string()].into_boxed_slice(), + ); map }, ..PiiConfig::default() @@ -533,7 +541,10 @@ mod tests { }, applications: { let mut map = BTreeMap::new(); - map.insert(ValueType::String.into(), vec!["0".to_owned()]); + map.insert( + ValueType::String.into(), + vec!["0".to_owned()].into_boxed_slice(), + ); map }, ..Default::default() @@ -899,7 +910,10 @@ mod tests { let config = PiiConfig { applications: { let mut map = BTreeMap::new(); - map.insert(ValueType::String.into(), vec!["@email:replace".to_owned()]); + map.insert( + ValueType::String.into(), + vec!["@email:replace".to_owned()].into_boxed_slice(), + ); map }, ..PiiConfig::default() diff --git a/relay-pii/src/config.rs b/relay-pii/src/config.rs index c5e7051a407..e193024de48 100644 --- a/relay-pii/src/config.rs +++ b/relay-pii/src/config.rs @@ -127,7 +127,7 @@ pub struct PatternRule { #[serde(rename_all = "camelCase")] pub struct MultipleRule { /// A reference to other rules to apply - pub rules: Vec, + pub rules: Box<[String]>, /// When set to true, the outer rule is reported. #[serde(default, skip_serializing_if = "is_flag_default")] pub hide_inner: bool, @@ -246,7 +246,7 @@ pub struct PiiConfig { /// Mapping of selectors to rules. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub applications: BTreeMap>, + pub applications: BTreeMap>, /// PII config derived from datascrubbing settings. /// diff --git a/relay-pii/src/convert.rs b/relay-pii/src/convert.rs index 0327f7b4d08..084822158e1 100644 --- a/relay-pii/src/convert.rs +++ b/relay-pii/src/convert.rs @@ -228,7 +228,10 @@ pub fn to_pii_config(datascrubbing_config: &DataScrubbingConfig) -> Option?!@#$%^&*())'gärbage'".to_owned()], + sensitive_fields: vec!["special ,./<>?!@#$%^&*())'gärbage'".to_owned()] + .into_boxed_slice(), exclude_fields: vec![ "do not ,./<>?!@#$%^&*())'ßtrip'".to_owned(), "2abc".to_owned(), - ], + ] + .into_boxed_slice(), ..simple_enabled_config() }); @@ -1583,7 +1591,7 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv #[test] fn test_regression_more_odd_keys() { let pii_config = to_pii_config(&DataScrubbingConfig { - sensitive_fields: vec![], + sensitive_fields: Box::new([]), exclude_fields: vec![ "url".to_owned(), "message".to_owned(), @@ -1591,7 +1599,8 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv "*url*".to_owned(), "*message*".to_owned(), "*http.request.url*".to_owned(), - ], + ] + .into_boxed_slice(), ..simple_enabled_config() }); @@ -1616,12 +1625,13 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv .into(), ); let pii_config = to_pii_config(&DataScrubbingConfig { - sensitive_fields: vec![], + sensitive_fields: Box::new([]), exclude_fields: vec![ "GITHUB_TOKEN".to_owned(), "access_token".to_owned(), "stripetoken".to_owned(), - ], + ] + .into_boxed_slice(), ..simple_enabled_config() }); @@ -1680,8 +1690,8 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv ); let pii_config = to_pii_config(&DataScrubbingConfig { - sensitive_fields: vec![], - exclude_fields: vec!["credentials".to_owned()], + sensitive_fields: Box::new([]), + exclude_fields: vec!["credentials".to_owned()].into_boxed_slice(), ..simple_enabled_config() }) .unwrap(); @@ -1899,7 +1909,8 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv "do_not_scrub_2.**".to_owned(), "extra.'do_not_scrub.dot'.**".to_owned(), "$user.id".to_owned(), - ], + ] + .into_boxed_slice(), ..simple_enabled_config() }) .unwrap(); @@ -1941,10 +1952,10 @@ THd+9FBxiHLGXNKhG/FRSyREXEt+NyYIf/0cyByc9tNksat794ddUqnLOg0vwSkv let pii_config = to_pii_config(&DataScrubbingConfig { // this triggered legacy behaviour and disabled scrubbing for all fields - exclude_fields: vec!["email".to_owned()], + exclude_fields: vec!["email".to_owned()].into_boxed_slice(), scrub_data: true, scrub_ip_addresses: true, - sensitive_fields: vec!["email".to_owned(), "password".to_owned()], + sensitive_fields: vec!["email".to_owned(), "password".to_owned()].into_boxed_slice(), scrub_defaults: true, ..Default::default() }) diff --git a/relay-pii/src/eap.rs b/relay-pii/src/eap.rs index 54c760ffa49..ae0d61c2073 100644 --- a/relay-pii/src/eap.rs +++ b/relay-pii/src/eap.rs @@ -169,11 +169,12 @@ mod tests { scrub_data: true, scrub_defaults: true, scrub_ip_addresses: true, - exclude_fields: vec!["public_data".to_owned()], + exclude_fields: vec!["public_data".to_owned()].into_boxed_slice(), sensitive_fields: vec![ "value".to_owned(), // Make sure the inner 'value' of the attribute object isn't scrubbed. "very_sensitive_data".to_owned(), - ], + ] + .into_boxed_slice(), ..Default::default() }; @@ -566,7 +567,8 @@ mod tests { "value".to_owned(), // Make sure the inner 'value' of the attribute object isn't scrubbed. "sensitive_custom".to_owned(), "another_sensitive".to_owned(), - ], + ] + .into_boxed_slice(), ..Default::default() }; @@ -665,7 +667,7 @@ mod tests { scrub_data: true, scrub_defaults: true, scrub_ip_addresses: false, - exclude_fields: vec!["secret".to_owned()], // Only 'secret' is safe + exclude_fields: vec!["secret".to_owned()].into_boxed_slice(), // Only 'secret' is safe ..Default::default() }; diff --git a/relay-pii/src/legacy.rs b/relay-pii/src/legacy.rs index c18b1d07e9f..b34a86f3950 100644 --- a/relay-pii/src/legacy.rs +++ b/relay-pii/src/legacy.rs @@ -15,7 +15,7 @@ use crate::convert; #[serde(default, rename_all = "camelCase")] pub struct DataScrubbingConfig { /// List with the fields to be excluded. - pub exclude_fields: Vec, + pub exclude_fields: Box<[String]>, /// Toggles all data scrubbing on or off. #[serde(skip_serializing_if = "crate::is_flag_default")] pub scrub_data: bool, @@ -23,7 +23,7 @@ pub struct DataScrubbingConfig { #[serde(skip_serializing_if = "crate::is_flag_default")] pub scrub_ip_addresses: bool, /// List of sensitive fields to be scrubbed from the messages. - pub sensitive_fields: Vec, + pub sensitive_fields: Box<[String]>, /// Controls whether default fields will be scrubbed. #[serde(skip_serializing_if = "crate::is_flag_default")] pub scrub_defaults: bool, @@ -46,10 +46,10 @@ impl DataScrubbingConfig { let _ = pii_config.set(None); DataScrubbingConfig { - exclude_fields: vec![], + exclude_fields: Box::new([]), scrub_data: false, scrub_ip_addresses: false, - sensitive_fields: vec![], + sensitive_fields: Box::new([]), scrub_defaults: false, pii_config, } diff --git a/relay-sampling/src/config.rs b/relay-sampling/src/config.rs index e2d44220127..559c11ff031 100644 --- a/relay-sampling/src/config.rs +++ b/relay-sampling/src/config.rs @@ -28,7 +28,7 @@ pub struct SamplingConfig { /// The ordered sampling rules for the project. #[serde(default)] - pub rules: Vec, + pub rules: Box<[SamplingRule]>, /// **Deprecated**. The ordered sampling rules for the project in legacy format. /// @@ -37,7 +37,7 @@ pub struct SamplingConfig { /// both an empty `rules` as well as the actual rules in `rules_v2`. During normalization, these /// two arrays are merged together. #[serde(default, skip_serializing)] - pub rules_v2: Vec, + pub rules_v2: Box<[SamplingRule]>, } impl SamplingConfig { @@ -60,7 +60,9 @@ impl SamplingConfig { /// Upgrades legacy sampling configs into the latest format. pub fn normalize(&mut self) { if self.version == Self::legacy_version() { - self.rules.append(&mut self.rules_v2); + let mut rules = std::mem::take(&mut self.rules).into_vec(); + rules.extend(std::mem::take(&mut self.rules_v2)); + self.rules = rules.into_boxed_slice(); self.version = SAMPLING_CONFIG_VERSION; } } @@ -74,8 +76,8 @@ impl Default for SamplingConfig { fn default() -> Self { Self { version: SAMPLING_CONFIG_VERSION, - rules: vec![], - rules_v2: vec![], + rules: Box::new([]), + rules_v2: Box::new([]), } } } @@ -473,7 +475,8 @@ mod tests { id: RuleId(1), time_range: Default::default(), decaying_fn: Default::default(), - }], + }] + .into_boxed_slice(), ..SamplingConfig::new() }; diff --git a/relay-server/src/metrics/rate_limits.rs b/relay-server/src/metrics/rate_limits.rs index d5877b8926b..aa7d3bd7a9d 100644 --- a/relay-server/src/metrics/rate_limits.rs +++ b/relay-server/src/metrics/rate_limits.rs @@ -10,7 +10,7 @@ use crate::utils; /// Contains all data necessary to rate limit metrics or metrics buckets. #[derive(Debug)] -pub struct MetricsLimiter> = Vec> { +pub struct MetricsLimiter = Vec> { /// A list of aggregated metric buckets with some counters. buckets: Vec, @@ -73,7 +73,7 @@ where } } -impl>> MetricsLimiter { +impl> MetricsLimiter { /// Create a new limiter instance. /// /// Returns Ok if `metrics` contain relevant metrics, `metrics` otherwise. diff --git a/relay-server/src/processing/spans/process.rs b/relay-server/src/processing/spans/process.rs index e871029051f..4e59144f8ab 100644 --- a/relay-server/src/processing/spans/process.rs +++ b/relay-server/src/processing/spans/process.rs @@ -242,7 +242,7 @@ fn normalize_span( if let Some(span) = span.value_mut() { let duration = span_duration(span); - let allowed_hosts = ctx.global_config.options.http_span_allowed_hosts.as_slice(); + let allowed_hosts = ctx.global_config.options.http_span_allowed_hosts.as_ref(); let model_metdata = ctx.global_config.ai_model_metadata(); let client_ua_info = settings.infer_user_agent.then(|| ClientUserAgentInfo { user_agent: meta.user_agent(), @@ -519,8 +519,9 @@ mod tests { datascrubbing_settings.sensitive_fields = vec![ "value".to_owned(), // Make sure the inner 'value' of the attribute object isn't scrubbed. "very_sensitive_data".to_owned(), - ]; - datascrubbing_settings.exclude_fields = vec!["public_data".to_owned()]; + ] + .into_boxed_slice(); + datascrubbing_settings.exclude_fields = vec!["public_data".to_owned()].into_boxed_slice(); let ctx = Context { project_info: &ProjectInfo { diff --git a/relay-server/src/processing/utils/dynamic_sampling.rs b/relay-server/src/processing/utils/dynamic_sampling.rs index 14c2eebfbbd..287715563d8 100644 --- a/relay-server/src/processing/utils/dynamic_sampling.rs +++ b/relay-server/src/processing/utils/dynamic_sampling.rs @@ -161,7 +161,8 @@ mod tests { id: RuleId(1), time_range: Default::default(), decaying_fn: DecayingFunction::Constant, - }], + }] + .into_boxed_slice(), ..SamplingConfig::new() }; @@ -189,7 +190,7 @@ mod tests { }; let sampling_config = SamplingConfig { - rules: vec![rule], + rules: vec![rule].into_boxed_slice(), ..SamplingConfig::new() }; @@ -226,7 +227,7 @@ mod tests { }; let sampling_config = SamplingConfig { - rules: vec![rule, unsupported_rule], + rules: vec![rule, unsupported_rule].into_boxed_slice(), ..SamplingConfig::new() }; @@ -254,7 +255,7 @@ mod tests { }; let sampling_config = SamplingConfig { - rules: vec![rule], + rules: vec![rule].into_boxed_slice(), ..SamplingConfig::new() }; diff --git a/relay-server/src/processing/utils/event.rs b/relay-server/src/processing/utils/event.rs index e511f0f0102..ccc709184e5 100644 --- a/relay-server/src/processing/utils/event.rs +++ b/relay-server/src/processing/utils/event.rs @@ -221,7 +221,7 @@ pub fn normalize( let transaction_aggregator_config = ctx.config.aggregator_config_for(MetricNamespace::Spans); let ai_model_metadata = ctx.global_config.ai_model_metadata(); - let http_span_allowed_hosts = ctx.global_config.options.http_span_allowed_hosts.as_slice(); + let http_span_allowed_hosts = ctx.global_config.options.http_span_allowed_hosts.as_ref(); let project_info = ctx.project_info; let retention_days: i64 = project_info @@ -285,7 +285,7 @@ pub fn normalize( is_renormalize: false, remove_other: full_normalization, emit_event_errors: full_normalization, - span_description_rules: project_info.config.span_description_rules.as_ref(), + span_description_rules: project_info.config.span_description_rules.as_deref(), geoip_lookup: Some(geoip_lookup), ai_model_metadata, enable_trimming: true, diff --git a/relay-server/src/services/processor.rs b/relay-server/src/services/processor.rs index c038dbb3ec6..f0fd7040e2f 100644 --- a/relay-server/src/services/processor.rs +++ b/relay-server/src/services/processor.rs @@ -1943,7 +1943,7 @@ mod tests { }; let mut config = ProjectConfig::default(); - config.quotas.push(quota); + config.quotas = vec![quota].into_boxed_slice(); Arc::new(ProjectInfo { config, diff --git a/relay-server/src/services/projects/project/info.rs b/relay-server/src/services/projects/project/info.rs index 22df7f61b7e..3d54d69c6a9 100644 --- a/relay-server/src/services/projects/project/info.rs +++ b/relay-server/src/services/projects/project/info.rs @@ -297,7 +297,7 @@ impl ProjectInfo { /// Returns quotas declared in this project state. pub fn get_quotas(&self) -> &[Quota] { - self.config.quotas.as_slice() + self.config.quotas.as_ref() } /// Validates data in this project state and removes values that are partially invalid.