cratestack_sql/descriptor/
mod.rs1use std::fmt::Write;
2use std::marker::PhantomData;
3
4use cratestack_core::ModelEventKind;
5use cratestack_policy::ReadPolicy;
6
7mod defaults;
8
9pub use defaults::{CreateDefault, CreateDefaultType};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ModelColumn {
13 pub rust_name: &'static str,
14 pub sql_name: &'static str,
15}
16
17#[derive(Debug, Clone, Copy)]
18pub struct ModelDescriptor<M, PK> {
19 pub schema_name: &'static str,
20 pub table_name: &'static str,
21 pub columns: &'static [ModelColumn],
22 pub primary_key: &'static str,
23 pub allowed_fields: &'static [&'static str],
24 pub allowed_includes: &'static [&'static str],
25 pub allowed_sorts: &'static [&'static str],
26 pub read_allow_policies: &'static [ReadPolicy],
27 pub read_deny_policies: &'static [ReadPolicy],
28 pub detail_allow_policies: &'static [ReadPolicy],
29 pub detail_deny_policies: &'static [ReadPolicy],
30 pub create_allow_policies: &'static [ReadPolicy],
31 pub create_deny_policies: &'static [ReadPolicy],
32 pub update_allow_policies: &'static [ReadPolicy],
33 pub update_deny_policies: &'static [ReadPolicy],
34 pub delete_allow_policies: &'static [ReadPolicy],
35 pub delete_deny_policies: &'static [ReadPolicy],
36 pub create_defaults: &'static [CreateDefault],
37 pub emitted_events: &'static [ModelEventKind],
38 pub version_column: Option<&'static str>,
42 pub audit_enabled: bool,
46 pub pii_columns: &'static [&'static str],
51 pub sensitive_columns: &'static [&'static str],
54 pub soft_delete_column: Option<&'static str>,
60 pub retention_days: Option<u32>,
65 pub upsert_update_columns: &'static [&'static str],
72 _marker: PhantomData<fn() -> (M, PK)>,
73}
74
75impl<M, PK> ModelDescriptor<M, PK> {
76 pub const fn new(
77 schema_name: &'static str,
78 table_name: &'static str,
79 columns: &'static [ModelColumn],
80 primary_key: &'static str,
81 allowed_fields: &'static [&'static str],
82 allowed_includes: &'static [&'static str],
83 allowed_sorts: &'static [&'static str],
84 read_allow_policies: &'static [ReadPolicy],
85 read_deny_policies: &'static [ReadPolicy],
86 detail_allow_policies: &'static [ReadPolicy],
87 detail_deny_policies: &'static [ReadPolicy],
88 create_allow_policies: &'static [ReadPolicy],
89 create_deny_policies: &'static [ReadPolicy],
90 update_allow_policies: &'static [ReadPolicy],
91 update_deny_policies: &'static [ReadPolicy],
92 delete_allow_policies: &'static [ReadPolicy],
93 delete_deny_policies: &'static [ReadPolicy],
94 create_defaults: &'static [CreateDefault],
95 emitted_events: &'static [ModelEventKind],
96 version_column: Option<&'static str>,
97 audit_enabled: bool,
98 pii_columns: &'static [&'static str],
99 sensitive_columns: &'static [&'static str],
100 soft_delete_column: Option<&'static str>,
101 retention_days: Option<u32>,
102 upsert_update_columns: &'static [&'static str],
103 ) -> Self {
104 Self {
105 schema_name,
106 table_name,
107 columns,
108 primary_key,
109 allowed_fields,
110 allowed_includes,
111 allowed_sorts,
112 read_allow_policies,
113 read_deny_policies,
114 detail_allow_policies,
115 detail_deny_policies,
116 create_allow_policies,
117 create_deny_policies,
118 update_allow_policies,
119 update_deny_policies,
120 delete_allow_policies,
121 delete_deny_policies,
122 create_defaults,
123 emitted_events,
124 version_column,
125 audit_enabled,
126 pii_columns,
127 sensitive_columns,
128 soft_delete_column,
129 retention_days,
130 upsert_update_columns,
131 _marker: PhantomData,
132 }
133 }
134
135 pub fn emits(&self, operation: ModelEventKind) -> bool {
136 self.emitted_events.contains(&operation)
137 }
138
139 pub fn select_projection(&self) -> String {
140 let mut sql = String::new();
141 for (index, column) in self.columns.iter().enumerate() {
142 if index > 0 {
143 sql.push_str(", ");
144 }
145 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
146 }
147 sql
148 }
149
150 pub fn select_projection_subset(&self, columns: &[&str]) -> String {
159 let mut sql = String::new();
160 let mut emitted = false;
161 for column in self.columns.iter() {
162 if columns.iter().any(|name| *name == column.sql_name) && {
163 if emitted {
164 sql.push_str(", ");
165 }
166 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
167 emitted = true;
168 true
169 } {}
170 }
171 if !emitted {
172 if let Some(pk_column) = self
178 .columns
179 .iter()
180 .find(|column| column.sql_name == self.primary_key)
181 {
182 let _ = write!(sql, "{} AS \"{}\"", pk_column.sql_name, pk_column.rust_name,);
183 }
184 }
185 sql
186 }
187}