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 embedded_defaults;
9mod model_impls;
10mod read_source;
11mod view;
12
13#[cfg(test)]
14mod tests_view;
15
16pub use defaults::{CreateDefault, CreateDefaultType, GeneratedDefault, GeneratedValue};
17pub use embedded_defaults::{DatabaseDefault, LiteralDefault};
18pub use read_source::{ReadSource, WriteSource};
19pub use view::ViewDescriptor;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct ModelColumn {
23 pub rust_name: &'static str,
24 pub sql_name: &'static str,
25}
26
27#[derive(Debug, Clone, Copy)]
28pub struct ModelDescriptor<M, PK> {
29 pub schema_name: &'static str,
30 pub table_name: &'static str,
31 pub columns: &'static [ModelColumn],
32 pub primary_key: &'static str,
33 pub allowed_fields: &'static [&'static str],
34 pub allowed_includes: &'static [&'static str],
35 pub allowed_sorts: &'static [&'static str],
36 pub read_allow_policies: &'static [ReadPolicy],
37 pub read_deny_policies: &'static [ReadPolicy],
38 pub detail_allow_policies: &'static [ReadPolicy],
39 pub detail_deny_policies: &'static [ReadPolicy],
40 pub create_allow_policies: &'static [ReadPolicy],
41 pub create_deny_policies: &'static [ReadPolicy],
42 pub update_allow_policies: &'static [ReadPolicy],
43 pub update_deny_policies: &'static [ReadPolicy],
44 pub delete_allow_policies: &'static [ReadPolicy],
45 pub delete_deny_policies: &'static [ReadPolicy],
46 pub create_defaults: &'static [CreateDefault],
47 pub generated_defaults: &'static [GeneratedDefault],
49 pub literal_defaults: &'static [LiteralDefault],
53 pub database_defaults: &'static [DatabaseDefault],
55 pub emitted_events: &'static [ModelEventKind],
56 pub version_column: Option<&'static str>,
60 pub audit_enabled: bool,
64 pub pii_columns: &'static [&'static str],
73 pub sensitive_columns: &'static [&'static str],
76 pub soft_delete_column: Option<&'static str>,
82 pub retention_days: Option<u32>,
87 pub upsert_update_columns: &'static [&'static str],
94 _marker: PhantomData<fn() -> (M, PK)>,
95}
96
97impl<M, PK> ModelDescriptor<M, PK> {
98 #[allow(clippy::too_many_arguments)]
101 pub const fn new(
102 schema_name: &'static str,
103 table_name: &'static str,
104 columns: &'static [ModelColumn],
105 primary_key: &'static str,
106 allowed_fields: &'static [&'static str],
107 allowed_includes: &'static [&'static str],
108 allowed_sorts: &'static [&'static str],
109 read_allow_policies: &'static [ReadPolicy],
110 read_deny_policies: &'static [ReadPolicy],
111 detail_allow_policies: &'static [ReadPolicy],
112 detail_deny_policies: &'static [ReadPolicy],
113 create_allow_policies: &'static [ReadPolicy],
114 create_deny_policies: &'static [ReadPolicy],
115 update_allow_policies: &'static [ReadPolicy],
116 update_deny_policies: &'static [ReadPolicy],
117 delete_allow_policies: &'static [ReadPolicy],
118 delete_deny_policies: &'static [ReadPolicy],
119 create_defaults: &'static [CreateDefault],
120 emitted_events: &'static [ModelEventKind],
121 version_column: Option<&'static str>,
122 audit_enabled: bool,
123 pii_columns: &'static [&'static str],
124 sensitive_columns: &'static [&'static str],
125 soft_delete_column: Option<&'static str>,
126 retention_days: Option<u32>,
127 upsert_update_columns: &'static [&'static str],
128 ) -> Self {
129 Self {
130 schema_name,
131 table_name,
132 columns,
133 primary_key,
134 allowed_fields,
135 allowed_includes,
136 allowed_sorts,
137 read_allow_policies,
138 read_deny_policies,
139 detail_allow_policies,
140 detail_deny_policies,
141 create_allow_policies,
142 create_deny_policies,
143 update_allow_policies,
144 update_deny_policies,
145 delete_allow_policies,
146 delete_deny_policies,
147 create_defaults,
148 generated_defaults: &[],
149 literal_defaults: &[],
150 database_defaults: &[],
151 emitted_events,
152 version_column,
153 audit_enabled,
154 pii_columns,
155 sensitive_columns,
156 soft_delete_column,
157 retention_days,
158 upsert_update_columns,
159 _marker: PhantomData,
160 }
161 }
162
163 pub fn emits(&self, operation: ModelEventKind) -> bool {
164 self.emitted_events.contains(&operation)
165 }
166
167 pub fn select_projection(&self) -> String {
168 let mut sql = String::new();
169 for (index, column) in self.columns.iter().enumerate() {
170 if index > 0 {
171 sql.push_str(", ");
172 }
173 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
174 }
175 sql
176 }
177
178 pub fn select_projection_subset(&self, columns: &[&str]) -> String {
187 let mut sql = String::new();
188 let mut emitted = false;
189 for column in self.columns.iter() {
190 if columns.contains(&column.sql_name) && {
191 if emitted {
192 sql.push_str(", ");
193 }
194 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
195 emitted = true;
196 true
197 } {}
198 }
199 if !emitted {
200 if let Some(pk_column) = self
206 .columns
207 .iter()
208 .find(|column| column.sql_name == self.primary_key)
209 {
210 let _ = write!(sql, "{} AS \"{}\"", pk_column.sql_name, pk_column.rust_name,);
211 }
212 }
213 sql
214 }
215}
216
217