Skip to main content

meta_language/query_algebra/
snapshot.rs

1use crate::{LinkNetwork, ParseConfiguration};
2
3use super::{LinkRule, LinkRuleRegistry};
4
5/// Expected outcome for a rule snapshot case.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum LinkRuleSnapshotExpectation {
8    /// The rule must match the source.
9    Valid,
10    /// The rule must not match the source.
11    Invalid,
12}
13
14/// One valid/invalid source case for a rule suite.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct LinkRuleSnapshotCase {
17    name: String,
18    source: String,
19    language: String,
20    expectation: LinkRuleSnapshotExpectation,
21}
22
23impl LinkRuleSnapshotCase {
24    /// Creates a snapshot case.
25    #[must_use]
26    pub fn new(
27        name: impl Into<String>,
28        source: impl Into<String>,
29        language: impl Into<String>,
30        expectation: LinkRuleSnapshotExpectation,
31    ) -> Self {
32        Self {
33            name: name.into(),
34            source: source.into(),
35            language: language.into(),
36            expectation,
37        }
38    }
39}
40
41/// Valid/invalid rule snapshot suite.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct LinkRuleSnapshotSuite {
44    rule: LinkRule,
45    cases: Vec<LinkRuleSnapshotCase>,
46}
47
48impl LinkRuleSnapshotSuite {
49    /// Creates a snapshot suite for `rule`.
50    #[must_use]
51    pub const fn new(rule: LinkRule) -> Self {
52        Self {
53            rule,
54            cases: Vec::new(),
55        }
56    }
57
58    /// Adds a case.
59    #[must_use]
60    pub fn with_case(mut self, case: LinkRuleSnapshotCase) -> Self {
61        self.cases.push(case);
62        self
63    }
64
65    /// Runs all cases against freshly parsed sources.
66    #[must_use]
67    pub fn run(
68        &self,
69        registry: &LinkRuleRegistry,
70        configuration: ParseConfiguration,
71    ) -> LinkRuleSnapshotReport {
72        let cases = self
73            .cases
74            .iter()
75            .map(|case| {
76                let network = LinkNetwork::parse(&case.source, &case.language, configuration);
77                let matches = self.rule.matches(&network, registry);
78                let has_match = !matches.is_empty();
79                let passed = match case.expectation {
80                    LinkRuleSnapshotExpectation::Valid => has_match,
81                    LinkRuleSnapshotExpectation::Invalid => !has_match,
82                };
83                LinkRuleSnapshotResult {
84                    name: case.name.clone(),
85                    expectation: case.expectation,
86                    matched: has_match,
87                    match_count: matches.len(),
88                    passed,
89                }
90            })
91            .collect();
92        LinkRuleSnapshotReport { cases }
93    }
94}
95
96/// Snapshot suite result.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct LinkRuleSnapshotReport {
99    cases: Vec<LinkRuleSnapshotResult>,
100}
101
102impl LinkRuleSnapshotReport {
103    /// Returns whether every case passed.
104    #[must_use]
105    pub fn is_success(&self) -> bool {
106        self.cases.iter().all(|case| case.passed)
107    }
108
109    /// Per-case results.
110    #[must_use]
111    pub fn cases(&self) -> &[LinkRuleSnapshotResult] {
112        &self.cases
113    }
114}
115
116/// One snapshot case result.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct LinkRuleSnapshotResult {
119    name: String,
120    expectation: LinkRuleSnapshotExpectation,
121    matched: bool,
122    match_count: usize,
123    passed: bool,
124}
125
126impl LinkRuleSnapshotResult {
127    /// Case name.
128    #[must_use]
129    pub fn name(&self) -> &str {
130        &self.name
131    }
132
133    /// Expected rule outcome.
134    #[must_use]
135    pub const fn expectation(&self) -> LinkRuleSnapshotExpectation {
136        self.expectation
137    }
138
139    /// Whether the rule matched the parsed source.
140    #[must_use]
141    pub const fn matched(&self) -> bool {
142        self.matched
143    }
144
145    /// Number of matches.
146    #[must_use]
147    pub const fn match_count(&self) -> usize {
148        self.match_count
149    }
150
151    /// Whether this case passed.
152    #[must_use]
153    pub const fn passed(&self) -> bool {
154        self.passed
155    }
156}