use relay_auth::PublicKey;
use relay_event_normalization::{
BreakdownsConfig, MeasurementsConfig, PerformanceScoreConfig, SpanDescriptionRule,
TransactionNameRule,
};
use relay_filter::ProjectFiltersConfig;
use relay_pii::{DataScrubbingConfig, PiiConfig};
use relay_quotas::Quota;
use relay_sampling::SamplingConfig;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error_boundary::ErrorBoundary;
use crate::feature::FeatureSet;
use crate::metrics::{
self, MetricExtractionConfig, Metrics, SessionMetricsConfig, TaggingRule,
TransactionMetricsConfig,
};
use crate::{defaults, GRADUATED_FEATURE_FLAGS};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ProjectConfig {
pub allowed_domains: Vec<String>,
pub trusted_relays: Vec<PublicKey>,
pub pii_config: Option<PiiConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grouping_config: Option<Value>,
#[serde(skip_serializing_if = "ProjectFiltersConfig::is_empty")]
pub filter_settings: ProjectFiltersConfig,
#[serde(skip_serializing_if = "DataScrubbingConfig::is_disabled")]
pub datascrubbing_settings: DataScrubbingConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_retention: Option<u16>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub quotas: Vec<Quota>,
#[serde(alias = "dynamicSampling", skip_serializing_if = "Option::is_none")]
pub sampling: Option<ErrorBoundary<SamplingConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub measurements: Option<MeasurementsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub breakdowns_v2: Option<BreakdownsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub performance_score: Option<PerformanceScoreConfig>,
#[serde(skip_serializing_if = "SessionMetricsConfig::is_disabled")]
pub session_metrics: SessionMetricsConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_metrics: Option<ErrorBoundary<TransactionMetricsConfig>>,
#[serde(default, skip_serializing_if = "skip_metrics_extraction")]
pub metric_extraction: ErrorBoundary<MetricExtractionConfig>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub metric_conditional_tagging: Vec<TaggingRule>,
#[serde(skip_serializing_if = "FeatureSet::is_empty")]
pub features: FeatureSet,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tx_name_rules: Vec<TransactionNameRule>,
#[serde(skip_serializing_if = "is_false")]
pub tx_name_ready: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub span_description_rules: Option<Vec<SpanDescriptionRule>>,
#[serde(default, skip_serializing_if = "skip_metrics")]
pub metrics: ErrorBoundary<Metrics>,
}
impl ProjectConfig {
pub fn sanitize(&mut self) {
self.quotas.retain(Quota::is_valid);
metrics::convert_conditional_tagging(self);
defaults::add_span_metrics(self);
if let Some(ErrorBoundary::Ok(ref mut sampling_config)) = self.sampling {
sampling_config.normalize();
}
for flag in GRADUATED_FEATURE_FLAGS {
self.features.0.insert(*flag);
}
}
}
impl Default for ProjectConfig {
fn default() -> Self {
ProjectConfig {
allowed_domains: vec!["*".to_string()],
trusted_relays: vec![],
pii_config: None,
grouping_config: None,
filter_settings: ProjectFiltersConfig::default(),
datascrubbing_settings: DataScrubbingConfig::default(),
event_retention: None,
quotas: Vec::new(),
sampling: None,
measurements: None,
breakdowns_v2: None,
performance_score: Default::default(),
session_metrics: SessionMetricsConfig::default(),
transaction_metrics: None,
metric_extraction: Default::default(),
metric_conditional_tagging: Vec::new(),
features: Default::default(),
tx_name_rules: Vec::new(),
tx_name_ready: false,
span_description_rules: None,
metrics: Default::default(),
}
}
}
fn skip_metrics_extraction(boundary: &ErrorBoundary<MetricExtractionConfig>) -> bool {
match boundary {
ErrorBoundary::Err(_) => true,
ErrorBoundary::Ok(config) => !config.is_enabled(),
}
}
fn skip_metrics(boundary: &ErrorBoundary<Metrics>) -> bool {
match boundary {
ErrorBoundary::Err(_) => true,
ErrorBoundary::Ok(metrics) => metrics.is_empty(),
}
}
#[allow(missing_docs)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase", remote = "ProjectConfig")]
pub struct LimitedProjectConfig {
pub allowed_domains: Vec<String>,
pub trusted_relays: Vec<PublicKey>,
pub pii_config: Option<PiiConfig>,
#[serde(skip_serializing_if = "ProjectFiltersConfig::is_empty")]
pub filter_settings: ProjectFiltersConfig,
#[serde(skip_serializing_if = "DataScrubbingConfig::is_disabled")]
pub datascrubbing_settings: DataScrubbingConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<ErrorBoundary<SamplingConfig>>,
#[serde(skip_serializing_if = "SessionMetricsConfig::is_disabled")]
pub session_metrics: SessionMetricsConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_metrics: Option<ErrorBoundary<TransactionMetricsConfig>>,
#[serde(default, skip_serializing_if = "skip_metrics_extraction")]
pub metric_extraction: ErrorBoundary<MetricExtractionConfig>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub metric_conditional_tagging: Vec<TaggingRule>,
#[serde(skip_serializing_if = "Option::is_none")]
pub measurements: Option<MeasurementsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub breakdowns_v2: Option<BreakdownsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub performance_score: Option<PerformanceScoreConfig>,
#[serde(skip_serializing_if = "FeatureSet::is_empty")]
pub features: FeatureSet,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tx_name_rules: Vec<TransactionNameRule>,
#[serde(skip_serializing_if = "is_false")]
pub tx_name_ready: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub span_description_rules: Option<Vec<SpanDescriptionRule>>,
}
fn is_false(value: &bool) -> bool {
!*value
}
#[cfg(test)]
mod tests {
use crate::Feature;
use super::*;
#[test]
fn graduated_feature_flag_gets_inserted() {
let mut project_config = ProjectConfig::default();
assert!(!project_config.features.has(Feature::UserReportV2Ingest));
project_config.sanitize();
assert!(project_config.features.has(Feature::UserReportV2Ingest));
}
}