Skip to main content

hydracache_db/
lint.rs

1#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2pub struct DeclaredRelation {
3    schema: Option<String>,
4    name: String,
5}
6
7impl DeclaredRelation {
8    pub fn table(name: impl Into<String>) -> Self {
9        Self {
10            schema: None,
11            name: normalize_ident(name.into()),
12        }
13    }
14
15    pub fn schema_table(schema: impl Into<String>, name: impl Into<String>) -> Self {
16        Self {
17            schema: Some(normalize_ident(schema.into())),
18            name: normalize_ident(name.into()),
19        }
20    }
21
22    pub fn schema(&self) -> Option<&str> {
23        self.schema.as_deref()
24    }
25
26    pub fn name(&self) -> &str {
27        &self.name
28    }
29}
30
31pub fn table(name: impl Into<String>) -> DeclaredRelation {
32    DeclaredRelation::table(name)
33}
34
35pub fn schema_table(schema: impl Into<String>, name: impl Into<String>) -> DeclaredRelation {
36    DeclaredRelation::schema_table(schema, name)
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum DeclaredLintMode {
41    Warn,
42    DenyMissingDependencies,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum LintFinding {
47    MissingDependencies,
48    ExtraDependencies,
49    Inconclusive,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct LintSuppression {
54    finding: LintFinding,
55    reason: String,
56}
57
58impl LintSuppression {
59    pub fn new(finding: LintFinding, reason: impl Into<String>) -> Self {
60        Self {
61            finding,
62            reason: reason.into(),
63        }
64    }
65
66    pub fn finding(&self) -> LintFinding {
67        self.finding
68    }
69
70    pub fn reason(&self) -> &str {
71        &self.reason
72    }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct PolicyLintMetadata {
77    pub(crate) sql: Option<String>,
78    pub(crate) declared: Vec<DeclaredRelation>,
79    pub(crate) mode: DeclaredLintMode,
80    pub(crate) suppressions: Vec<LintSuppression>,
81}
82
83impl Default for PolicyLintMetadata {
84    fn default() -> Self {
85        Self {
86            sql: None,
87            declared: Vec::new(),
88            mode: DeclaredLintMode::Warn,
89            suppressions: Vec::new(),
90        }
91    }
92}
93
94impl PolicyLintMetadata {
95    pub fn new() -> Self {
96        Self::default()
97    }
98
99    pub fn sql(&self) -> Option<&str> {
100        self.sql.as_deref()
101    }
102
103    pub fn declared(&self) -> &[DeclaredRelation] {
104        &self.declared
105    }
106
107    pub fn mode(&self) -> DeclaredLintMode {
108        self.mode
109    }
110
111    pub fn suppressions(&self) -> &[LintSuppression] {
112        &self.suppressions
113    }
114
115    pub fn with_sql(mut self, sql: impl Into<String>) -> Self {
116        self.sql = Some(sql.into());
117        self
118    }
119
120    pub fn with_mode(mut self, mode: DeclaredLintMode) -> Self {
121        self.mode = mode;
122        self
123    }
124
125    pub fn with_declared(mut self, relation: DeclaredRelation) -> Self {
126        self.declared.push(relation);
127        self
128    }
129
130    pub fn with_suppression(mut self, finding: LintFinding, reason: impl Into<String>) -> Self {
131        self.suppressions
132            .push(LintSuppression::new(finding, reason));
133        self
134    }
135}
136
137fn normalize_ident(identifier: impl Into<String>) -> String {
138    identifier
139        .into()
140        .trim()
141        .trim_matches('"')
142        .trim_matches('`')
143        .trim_matches('[')
144        .trim_matches(']')
145        .to_ascii_lowercase()
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn table_metadata_normalizes_names() {
154        let relation = schema_table("App", "\"Users\"");
155
156        assert_eq!(relation.schema(), Some("app"));
157        assert_eq!(relation.name(), "users");
158    }
159}