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],
59 pub sensitive_columns: &'static [&'static str],
62 pub soft_delete_column: Option<&'static str>,
68 pub retention_days: Option<u32>,
73 pub upsert_update_columns: &'static [&'static str],
80 _marker: PhantomData<fn() -> (M, PK)>,
81}
82
83impl<M, PK> ModelDescriptor<M, PK> {
84 pub const fn new(
85 schema_name: &'static str,
86 table_name: &'static str,
87 columns: &'static [ModelColumn],
88 primary_key: &'static str,
89 allowed_fields: &'static [&'static str],
90 allowed_includes: &'static [&'static str],
91 allowed_sorts: &'static [&'static str],
92 read_allow_policies: &'static [ReadPolicy],
93 read_deny_policies: &'static [ReadPolicy],
94 detail_allow_policies: &'static [ReadPolicy],
95 detail_deny_policies: &'static [ReadPolicy],
96 create_allow_policies: &'static [ReadPolicy],
97 create_deny_policies: &'static [ReadPolicy],
98 update_allow_policies: &'static [ReadPolicy],
99 update_deny_policies: &'static [ReadPolicy],
100 delete_allow_policies: &'static [ReadPolicy],
101 delete_deny_policies: &'static [ReadPolicy],
102 create_defaults: &'static [CreateDefault],
103 emitted_events: &'static [ModelEventKind],
104 version_column: Option<&'static str>,
105 audit_enabled: bool,
106 pii_columns: &'static [&'static str],
107 sensitive_columns: &'static [&'static str],
108 soft_delete_column: Option<&'static str>,
109 retention_days: Option<u32>,
110 upsert_update_columns: &'static [&'static str],
111 ) -> Self {
112 Self {
113 schema_name,
114 table_name,
115 columns,
116 primary_key,
117 allowed_fields,
118 allowed_includes,
119 allowed_sorts,
120 read_allow_policies,
121 read_deny_policies,
122 detail_allow_policies,
123 detail_deny_policies,
124 create_allow_policies,
125 create_deny_policies,
126 update_allow_policies,
127 update_deny_policies,
128 delete_allow_policies,
129 delete_deny_policies,
130 create_defaults,
131 emitted_events,
132 version_column,
133 audit_enabled,
134 pii_columns,
135 sensitive_columns,
136 soft_delete_column,
137 retention_days,
138 upsert_update_columns,
139 _marker: PhantomData,
140 }
141 }
142
143 pub fn emits(&self, operation: ModelEventKind) -> bool {
144 self.emitted_events.contains(&operation)
145 }
146
147 pub fn select_projection(&self) -> String {
148 let mut sql = String::new();
149 for (index, column) in self.columns.iter().enumerate() {
150 if index > 0 {
151 sql.push_str(", ");
152 }
153 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
154 }
155 sql
156 }
157
158 pub fn select_projection_subset(&self, columns: &[&str]) -> String {
167 let mut sql = String::new();
168 let mut emitted = false;
169 for column in self.columns.iter() {
170 if columns.iter().any(|name| *name == column.sql_name) && {
171 if emitted {
172 sql.push_str(", ");
173 }
174 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
175 emitted = true;
176 true
177 } {}
178 }
179 if !emitted {
180 if let Some(pk_column) = self
186 .columns
187 .iter()
188 .find(|column| column.sql_name == self.primary_key)
189 {
190 let _ = write!(sql, "{} AS \"{}\"", pk_column.sql_name, pk_column.rust_name,);
191 }
192 }
193 sql
194 }
195}
196
197