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}