cratestack_sql/descriptor/
mod.rs1use std::fmt::Write;
2use std::marker::PhantomData;
3
4use cratestack_core::ModelEventKind;
5use cratestack_policy::ReadPolicy;
6
7mod defaults;
8mod model_impls;
9mod read_source;
10mod view;
11
12#[cfg(test)]
13mod tests_view;
14
15pub use defaults::{CreateDefault, CreateDefaultType};
16pub use read_source::{ReadSource, WriteSource};
17pub use view::ViewDescriptor;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct ModelColumn {
21 pub rust_name: &'static str,
22 pub sql_name: &'static str,
23}
24
25#[derive(Debug, Clone, Copy)]
26pub struct ModelDescriptor<M, PK> {
27 pub schema_name: &'static str,
28 pub table_name: &'static str,
29 pub columns: &'static [ModelColumn],
30 pub primary_key: &'static str,
31 pub allowed_fields: &'static [&'static str],
32 pub allowed_includes: &'static [&'static str],
33 pub allowed_sorts: &'static [&'static str],
34 pub read_allow_policies: &'static [ReadPolicy],
35 pub read_deny_policies: &'static [ReadPolicy],
36 pub detail_allow_policies: &'static [ReadPolicy],
37 pub detail_deny_policies: &'static [ReadPolicy],
38 pub create_allow_policies: &'static [ReadPolicy],
39 pub create_deny_policies: &'static [ReadPolicy],
40 pub update_allow_policies: &'static [ReadPolicy],
41 pub update_deny_policies: &'static [ReadPolicy],
42 pub delete_allow_policies: &'static [ReadPolicy],
43 pub delete_deny_policies: &'static [ReadPolicy],
44 pub create_defaults: &'static [CreateDefault],
45 pub emitted_events: &'static [ModelEventKind],
46 pub version_column: Option<&'static str>,
50 pub audit_enabled: bool,
54 pub pii_columns: &'static [&'static str],
63 pub sensitive_columns: &'static [&'static str],
66 pub soft_delete_column: Option<&'static str>,
72 pub retention_days: Option<u32>,
77 pub upsert_update_columns: &'static [&'static str],
84 _marker: PhantomData<fn() -> (M, PK)>,
85}
86
87impl<M, PK> ModelDescriptor<M, PK> {
88 #[allow(clippy::too_many_arguments)]
91 pub const fn new(
92 schema_name: &'static str,
93 table_name: &'static str,
94 columns: &'static [ModelColumn],
95 primary_key: &'static str,
96 allowed_fields: &'static [&'static str],
97 allowed_includes: &'static [&'static str],
98 allowed_sorts: &'static [&'static str],
99 read_allow_policies: &'static [ReadPolicy],
100 read_deny_policies: &'static [ReadPolicy],
101 detail_allow_policies: &'static [ReadPolicy],
102 detail_deny_policies: &'static [ReadPolicy],
103 create_allow_policies: &'static [ReadPolicy],
104 create_deny_policies: &'static [ReadPolicy],
105 update_allow_policies: &'static [ReadPolicy],
106 update_deny_policies: &'static [ReadPolicy],
107 delete_allow_policies: &'static [ReadPolicy],
108 delete_deny_policies: &'static [ReadPolicy],
109 create_defaults: &'static [CreateDefault],
110 emitted_events: &'static [ModelEventKind],
111 version_column: Option<&'static str>,
112 audit_enabled: bool,
113 pii_columns: &'static [&'static str],
114 sensitive_columns: &'static [&'static str],
115 soft_delete_column: Option<&'static str>,
116 retention_days: Option<u32>,
117 upsert_update_columns: &'static [&'static str],
118 ) -> Self {
119 Self {
120 schema_name,
121 table_name,
122 columns,
123 primary_key,
124 allowed_fields,
125 allowed_includes,
126 allowed_sorts,
127 read_allow_policies,
128 read_deny_policies,
129 detail_allow_policies,
130 detail_deny_policies,
131 create_allow_policies,
132 create_deny_policies,
133 update_allow_policies,
134 update_deny_policies,
135 delete_allow_policies,
136 delete_deny_policies,
137 create_defaults,
138 emitted_events,
139 version_column,
140 audit_enabled,
141 pii_columns,
142 sensitive_columns,
143 soft_delete_column,
144 retention_days,
145 upsert_update_columns,
146 _marker: PhantomData,
147 }
148 }
149
150 pub fn emits(&self, operation: ModelEventKind) -> bool {
151 self.emitted_events.contains(&operation)
152 }
153
154 pub fn select_projection(&self) -> String {
155 let mut sql = String::new();
156 for (index, column) in self.columns.iter().enumerate() {
157 if index > 0 {
158 sql.push_str(", ");
159 }
160 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
161 }
162 sql
163 }
164
165 pub fn select_projection_subset(&self, columns: &[&str]) -> String {
174 let mut sql = String::new();
175 let mut emitted = false;
176 for column in self.columns.iter() {
177 if columns.contains(&column.sql_name) && {
178 if emitted {
179 sql.push_str(", ");
180 }
181 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
182 emitted = true;
183 true
184 } {}
185 }
186 if !emitted {
187 if let Some(pk_column) = self
193 .columns
194 .iter()
195 .find(|column| column.sql_name == self.primary_key)
196 {
197 let _ = write!(sql, "{} AS \"{}\"", pk_column.sql_name, pk_column.rust_name,);
198 }
199 }
200 sql
201 }
202}
203
204