scrut/rules/
rule.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::fmt::Debug;
9use std::fmt::Display;
10
11use anyhow::Result;
12use serde::ser::SerializeMap;
13use serde::Serialize;
14
15use crate::escaping::Escaper;
16
17/// Rule implements the line-level comparisons of [`crate::expectation::Expectation`]s
18pub trait Rule: RuleClone + Debug + Send {
19    /// What kind (type name) the rule has
20    fn kind(&self) -> &'static str;
21
22    /// Whether the rule matches - can be applied to - the given line
23    fn matches(&self, line: &[u8]) -> bool;
24
25    /// Decompose the rule into components from which it can be re-made
26    fn unmake(&self) -> (String, Vec<u8>);
27
28    /// The string representation of the Rule as it would be written in
29    /// a test document
30    fn to_expression_string(&self, optional: bool, multiline: bool, escaper: &Escaper) -> String {
31        let (quantifier, equal_quantifier) = if optional {
32            if multiline {
33                ("*", " (*)")
34            } else {
35                ("?", " (?)")
36            }
37        } else if multiline {
38            ("+", " (+)")
39        } else {
40            ("", "")
41        };
42        let (kind, expression) = self.unmake();
43        let rendered = escaper.escaped_printable(&expression);
44        if kind == "equal" {
45            if escaper.has_unprintable(&expression) {
46                format!("{rendered} (escaped{quantifier})")
47            } else {
48                format!("{rendered}{equal_quantifier}")
49            }
50        } else {
51            format!("{rendered} ({kind}{quantifier})")
52        }
53    }
54}
55
56impl Display for Box<dyn Rule> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        let (kind, expression) = self.unmake();
59        let expression = Escaper::Unicode.escaped_printable(&expression);
60        write!(f, "{kind}::{expression}")
61    }
62}
63
64impl PartialEq<Box<dyn Rule>> for Box<dyn Rule> {
65    fn eq(&self, other: &Box<dyn Rule>) -> bool {
66        format!("{self}") == format!("{other}")
67    }
68}
69
70impl Clone for Box<dyn Rule> {
71    fn clone(&self) -> Self {
72        self.clone_box()
73    }
74}
75
76impl Serialize for Box<dyn Rule> {
77    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78    where
79        S: serde::Serializer,
80    {
81        let (kind, expression) = self.unmake();
82        let expression = Escaper::Unicode.escaped_printable(&expression);
83        let mut map = serializer.serialize_map(Some(2))?;
84        map.serialize_entry("kind", &kind)?;
85        map.serialize_entry("expression", &expression)?;
86        map.end()
87    }
88}
89
90// serialize_trait_object!(Rule);
91
92pub trait RuleClone {
93    fn clone_box(&self) -> Box<dyn Rule>;
94}
95
96impl<T: 'static + Rule + Clone> RuleClone for T {
97    fn clone_box(&self) -> Box<dyn Rule> {
98        Box::new(self.clone())
99    }
100}
101
102/// Trait
103pub trait RuleMaker {
104    fn make(expression: &str) -> Result<Box<dyn Rule>>;
105}
106
107/// Constructor function for [`Rule`] implementations
108pub type MakeRule = fn(&str) -> Result<Box<dyn Rule>>;