toasty_core/schema/app/
schema.rs1use super::{EnumVariant, Field, FieldId, FieldTy, Model, ModelId, VariantId};
2
3use crate::{stmt, Result};
4use indexmap::IndexMap;
5
6#[derive(Debug)]
12pub enum Resolved<'a> {
13 Field(&'a Field),
15 Variant(&'a EnumVariant),
17}
18
19#[derive(Debug, Default)]
20pub struct Schema {
21 pub models: IndexMap<ModelId, Model>,
22}
23
24#[derive(Default)]
25struct Builder {
26 models: IndexMap<ModelId, Model>,
27}
28
29impl Schema {
30 pub fn from_macro(models: &[Model]) -> Result<Self> {
31 Builder::from_macro(models)
32 }
33
34 pub fn field(&self, id: FieldId) -> &Field {
36 let fields = match self.model(id.model) {
37 Model::Root(root) => &root.fields,
38 Model::EmbeddedStruct(embedded) => &embedded.fields,
39 Model::EmbeddedEnum(e) => &e.fields,
40 };
41 fields.get(id.index).expect("invalid field ID")
42 }
43
44 pub fn variant(&self, id: VariantId) -> &EnumVariant {
46 let Model::EmbeddedEnum(e) = self.model(id.model) else {
47 panic!("VariantId references a non-enum model");
48 };
49 e.variants.get(id.index).expect("invalid variant index")
50 }
51
52 pub fn models(&self) -> impl Iterator<Item = &Model> {
53 self.models.values()
54 }
55
56 pub fn get_model(&self, id: impl Into<ModelId>) -> Option<&Model> {
58 self.models.get(&id.into())
59 }
60
61 pub fn model(&self, id: impl Into<ModelId>) -> &Model {
63 self.models.get(&id.into()).expect("invalid model ID")
64 }
65
66 pub fn resolve<'a>(
78 &'a self,
79 root: &'a Model,
80 projection: &stmt::Projection,
81 ) -> Option<Resolved<'a>> {
82 let [first, rest @ ..] = projection.as_slice() else {
83 return None;
84 };
85
86 let mut current_field = root.expect_root().fields.get(*first)?;
88
89 let mut steps = rest.iter();
92 while let Some(step) = steps.next() {
93 match ¤t_field.ty {
94 FieldTy::Primitive(..) => {
95 return None;
97 }
98 FieldTy::Embedded(embedded) => {
99 let target = self.model(embedded.target);
100 match target {
101 Model::EmbeddedStruct(s) => {
102 current_field = s.fields.get(*step)?;
103 }
104 Model::EmbeddedEnum(e) => {
105 let variant = e
106 .variants
107 .iter()
108 .find(|v| v.discriminant as usize == *step)?;
109
110 if let Some(field_step) = steps.next() {
112 current_field = e.fields.get(*field_step)?;
114 } else {
115 return Some(Resolved::Variant(variant));
117 }
118 }
119 _ => return None,
120 }
121 }
122 FieldTy::BelongsTo(belongs_to) => {
123 current_field = belongs_to.target(self).expect_root().fields.get(*step)?;
124 }
125 FieldTy::HasMany(has_many) => {
126 current_field = has_many.target(self).expect_root().fields.get(*step)?;
127 }
128 FieldTy::HasOne(has_one) => {
129 current_field = has_one.target(self).expect_root().fields.get(*step)?;
130 }
131 };
132 }
133
134 Some(Resolved::Field(current_field))
135 }
136
137 pub fn resolve_field<'a>(
142 &'a self,
143 root: &'a Model,
144 projection: &stmt::Projection,
145 ) -> Option<&'a Field> {
146 match self.resolve(root, projection) {
147 Some(Resolved::Field(field)) => Some(field),
148 _ => None,
149 }
150 }
151
152 pub fn resolve_field_path<'a>(&'a self, path: &stmt::Path) -> Option<&'a Field> {
153 let model = self.model(path.root.expect_model());
154 self.resolve_field(model, &path.projection)
155 }
156}
157
158impl Builder {
159 pub(crate) fn from_macro(models: &[Model]) -> Result<Schema> {
160 let mut builder = Self { ..Self::default() };
161
162 for model in models {
163 builder.models.insert(model.id(), model.clone());
164 }
165
166 builder.process_models()?;
167 builder.into_schema()
168 }
169
170 fn into_schema(self) -> Result<Schema> {
171 Ok(Schema {
172 models: self.models,
173 })
174 }
175
176 fn process_models(&mut self) -> Result<()> {
177 self.link_relations()?;
180
181 Ok(())
182 }
183
184 fn link_relations(&mut self) -> crate::Result<()> {
186 for curr in 0..self.models.len() {
194 if self.models[curr].is_embedded() {
195 continue;
196 }
197 for index in 0..self.models[curr].expect_root().fields.len() {
198 let model = &self.models[curr];
199 let src = model.id();
200 let field = &model.expect_root().fields[index];
201
202 if let FieldTy::HasMany(has_many) = &field.ty {
203 let target = has_many.target;
204 let field_name = field.name.app_name.clone();
205 let pair = self.find_has_many_pair(src, target, &field_name)?;
206 self.models[curr].expect_root_mut().fields[index]
207 .ty
208 .expect_has_many_mut()
209 .pair = pair;
210 }
211 }
212 }
213
214 for curr in 0..self.models.len() {
216 if self.models[curr].is_embedded() {
217 continue;
218 }
219 for index in 0..self.models[curr].expect_root().fields.len() {
220 let model = &self.models[curr];
221 let src = model.id();
222 let field = &model.expect_root().fields[index];
223
224 match &field.ty {
225 FieldTy::HasOne(has_one) => {
226 let target = has_one.target;
227 let field_name = field.name.app_name.clone();
228 let pair = match self.find_belongs_to_pair(src, target, &field_name)? {
229 Some(pair) => pair,
230 None => {
231 return Err(crate::Error::invalid_schema(format!(
232 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
233 self.models[curr].name().upper_camel_case(),
234 field_name,
235 )));
236 }
237 };
238
239 self.models[curr].expect_root_mut().fields[index]
240 .ty
241 .expect_has_one_mut()
242 .pair = pair;
243 }
244 FieldTy::BelongsTo(belongs_to) => {
245 assert!(!belongs_to.foreign_key.is_placeholder());
246 continue;
247 }
248 _ => {}
249 }
250 }
251 }
252
253 for curr in 0..self.models.len() {
255 if self.models[curr].is_embedded() {
256 continue;
257 }
258 for index in 0..self.models[curr].expect_root().fields.len() {
259 let model = &self.models[curr];
260 let field_id = model.expect_root().fields[index].id;
261
262 let pair = match &self.models[curr].expect_root().fields[index].ty {
263 FieldTy::BelongsTo(belongs_to) => {
264 let mut pair = None;
265 let target = match self.models.get_index_of(&belongs_to.target) {
266 Some(target) => target,
267 None => {
268 let model = &self.models[curr];
269 return Err(crate::Error::invalid_schema(format!(
270 "field `{}::{}` references a model that was not registered \
271 with the schema; did you forget to register it with `Db::builder()`?",
272 model.name().upper_camel_case(),
273 model.expect_root().fields[index].name.app_name,
274 )));
275 }
276 };
277
278 for target_index in 0..self.models[target].expect_root().fields.len() {
279 pair = match &self.models[target].expect_root().fields[target_index].ty
280 {
281 FieldTy::HasMany(has_many) if has_many.pair == field_id => {
282 assert!(pair.is_none());
283 Some(self.models[target].expect_root().fields[target_index].id)
284 }
285 FieldTy::HasOne(has_one) if has_one.pair == field_id => {
286 assert!(pair.is_none());
287 Some(self.models[target].expect_root().fields[target_index].id)
288 }
289 _ => continue,
290 }
291 }
292
293 if pair.is_none() {
294 continue;
295 }
296
297 pair
298 }
299 _ => continue,
300 };
301
302 self.models[curr].expect_root_mut().fields[index]
303 .ty
304 .expect_belongs_to_mut()
305 .pair = pair;
306 }
307 }
308
309 Ok(())
310 }
311
312 fn find_belongs_to_pair(
313 &self,
314 src: ModelId,
315 target: ModelId,
316 field_name: &str,
317 ) -> crate::Result<Option<FieldId>> {
318 let src_model = &self.models[&src];
319
320 let target = match self.models.get(&target) {
321 Some(target) => target,
322 None => {
323 return Err(crate::Error::invalid_schema(format!(
324 "field `{}::{}` references a model that was not registered with the schema; \
325 did you forget to register it with `Db::builder()`?",
326 src_model.name().upper_camel_case(),
327 field_name,
328 )));
329 }
330 };
331
332 let belongs_to: Vec<_> = target
334 .expect_root()
335 .fields
336 .iter()
337 .filter(|field| match &field.ty {
338 FieldTy::BelongsTo(rel) => rel.target == src,
339 _ => false,
340 })
341 .collect();
342
343 match &belongs_to[..] {
344 [field] => Ok(Some(field.id)),
345 [] => Ok(None),
346 _ => Err(crate::Error::invalid_schema(format!(
347 "model `{}` has more than one `BelongsTo` relation targeting `{}`",
348 target.name().upper_camel_case(),
349 src_model.name().upper_camel_case(),
350 ))),
351 }
352 }
353
354 fn find_has_many_pair(
355 &mut self,
356 src: ModelId,
357 target: ModelId,
358 field_name: &str,
359 ) -> crate::Result<FieldId> {
360 if let Some(field_id) = self.find_belongs_to_pair(src, target, field_name)? {
361 return Ok(field_id);
362 }
363
364 Err(crate::Error::invalid_schema(format!(
365 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
366 self.models[&src].name().upper_camel_case(),
367 field_name,
368 )))
369 }
370}