Skip to main content

dataprof_core/
analysis_options.rs

1//! The analysis selection every parser and engine must honour.
2//!
3//! Metric packs, quality dimensions, locale, and semantic hints are all
4//! *requests about what to compute*, and the report they produce has to be the
5//! same on every input path. Passing them as loose parameters made that easy to
6//! get wrong: a parser that took dimensions and hints but not packs or locale
7//! compiled fine and silently computed work the caller had deselected.
8//! [`AnalysisOptions`] bundles them so a path either carries the whole selection
9//! or does not compile.
10
11use crate::locale::Locale;
12use crate::quality::{MetricPack, QualityDimension};
13use crate::semantic::SemanticHints;
14
15/// What to analyze, and how, for a single profiling run.
16///
17/// Construct with [`AnalysisOptions::default`] (analyze everything) and narrow
18/// with the builder methods.
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct AnalysisOptions {
21    metric_packs: Option<Vec<MetricPack>>,
22    quality_dimensions: Option<Vec<QualityDimension>>,
23    locale: Option<Locale>,
24    semantic_hints: SemanticHints,
25}
26
27impl AnalysisOptions {
28    /// Select the metric packs to compute. `None` (the default) means all.
29    pub fn with_metric_packs(mut self, packs: Option<Vec<MetricPack>>) -> Self {
30        self.metric_packs = packs;
31        self
32    }
33
34    /// Select the quality dimensions to assess. `None` (the default) means all.
35    pub fn with_quality_dimensions(mut self, dimensions: Option<Vec<QualityDimension>>) -> Self {
36        self.quality_dimensions = dimensions;
37        self
38    }
39
40    /// Set the locale used to rank detected patterns.
41    ///
42    /// Parse a user-supplied tag with [`Locale::parse_optional`] first, so an
43    /// unrecognised tag is rejected where it is written rather than silently
44    /// suppressing every locale-specific pattern.
45    pub fn with_locale(mut self, locale: Option<Locale>) -> Self {
46        self.locale = locale;
47        self
48    }
49
50    /// Set the user's semantic hints.
51    pub fn with_semantic_hints(mut self, hints: SemanticHints) -> Self {
52        self.semantic_hints = hints;
53        self
54    }
55
56    /// The packs to compute, with an empty quality-dimension selection folded in.
57    ///
58    /// Resolved on read rather than in the setters so the outcome does not
59    /// depend on the order the selections were made in.
60    pub fn effective_metric_packs(&self) -> Option<Vec<MetricPack>> {
61        MetricPack::resolve_with_dimensions(
62            self.metric_packs.as_deref(),
63            self.quality_dimensions.as_deref(),
64        )
65    }
66
67    /// Whether per-column statistics should be computed.
68    pub fn include_statistics(&self) -> bool {
69        MetricPack::include_statistics(self.effective_metric_packs().as_deref())
70    }
71
72    /// Whether pattern detection should run.
73    pub fn include_patterns(&self) -> bool {
74        MetricPack::include_patterns(self.effective_metric_packs().as_deref())
75    }
76
77    /// Whether quality metrics should be computed.
78    ///
79    /// `false` means the report carries no quality at all — absent, not an
80    /// empty assessment — because nothing was analyzed.
81    pub fn include_quality(&self) -> bool {
82        MetricPack::include_quality(self.effective_metric_packs().as_deref())
83    }
84
85    /// The locale used to rank detected patterns.
86    ///
87    /// A locale only ranks patterns, so it has no effect of its own when
88    /// [`include_patterns`](Self::include_patterns) is false and detection never
89    /// runs.
90    pub fn locale(&self) -> Option<Locale> {
91        self.locale
92    }
93
94    /// The requested quality dimensions, if the caller narrowed them.
95    pub fn quality_dimensions(&self) -> Option<&[QualityDimension]> {
96        self.quality_dimensions.as_deref()
97    }
98
99    /// The user's semantic hints.
100    pub fn semantic_hints(&self) -> &SemanticHints {
101        &self.semantic_hints
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn default_analyzes_everything() {
111        let options = AnalysisOptions::default();
112        assert!(options.include_statistics());
113        assert!(options.include_patterns());
114        assert!(options.include_quality());
115        assert_eq!(options.locale(), None);
116        assert_eq!(options.quality_dimensions(), None);
117    }
118
119    #[test]
120    fn schema_only_deselects_every_other_pack() {
121        let options = AnalysisOptions::default().with_metric_packs(Some(vec![MetricPack::Schema]));
122        assert!(!options.include_statistics());
123        assert!(!options.include_patterns());
124        assert!(!options.include_quality());
125    }
126
127    #[test]
128    fn empty_dimension_selection_removes_the_quality_pack() {
129        let options = AnalysisOptions::default().with_quality_dimensions(Some(vec![]));
130        assert!(!options.include_quality());
131        // Deselecting quality says nothing about the other packs.
132        assert!(options.include_statistics());
133        assert!(options.include_patterns());
134    }
135
136    #[test]
137    fn resolution_does_not_depend_on_setter_order() {
138        let packs = vec![MetricPack::Schema, MetricPack::Quality];
139        let dims_first = AnalysisOptions::default()
140            .with_quality_dimensions(Some(vec![]))
141            .with_metric_packs(Some(packs.clone()));
142        let packs_first = AnalysisOptions::default()
143            .with_metric_packs(Some(packs))
144            .with_quality_dimensions(Some(vec![]));
145        assert_eq!(
146            dims_first.effective_metric_packs(),
147            packs_first.effective_metric_packs()
148        );
149        assert!(!dims_first.include_quality());
150    }
151
152    #[test]
153    fn a_locale_alone_does_not_re_enable_pattern_detection() {
154        let options = AnalysisOptions::default()
155            .with_locale(Some(Locale::It))
156            .with_metric_packs(Some(vec![MetricPack::Schema]));
157        assert_eq!(options.locale(), Some(Locale::It));
158        assert!(!options.include_patterns());
159    }
160}