toasty_core/schema/app/
schema.rs1use super::{EnumVariant, Field, FieldId, FieldTy, Model, ModelId, VariantId};
2
3use crate::{Result, stmt};
4use indexmap::IndexMap;
5
6#[derive(Debug)]
25pub enum Resolved<'a> {
26 Field(&'a Field),
28 Variant(&'a EnumVariant),
30}
31
32#[derive(Debug, Default)]
49pub struct Schema {
50 pub models: IndexMap<ModelId, Model>,
52}
53
54#[derive(Default)]
55struct Builder {
56 models: IndexMap<ModelId, Model>,
57}
58
59impl Schema {
60 pub fn from_macro(models: impl IntoIterator<Item = Model>) -> Result<Self> {
65 Builder::from_macro(models)
66 }
67
68 pub fn field(&self, id: FieldId) -> &Field {
74 let fields = match self.model(id.model) {
75 Model::Root(root) => &root.fields,
76 Model::EmbeddedStruct(embedded) => &embedded.fields,
77 Model::EmbeddedEnum(e) => &e.fields,
78 };
79 fields.get(id.index).expect("invalid field ID")
80 }
81
82 pub fn variant(&self, id: VariantId) -> &EnumVariant {
89 let Model::EmbeddedEnum(e) = self.model(id.model) else {
90 panic!("VariantId references a non-enum model");
91 };
92 e.variants.get(id.index).expect("invalid variant index")
93 }
94
95 pub fn models(&self) -> impl Iterator<Item = &Model> {
97 self.models.values()
98 }
99
100 pub fn get_model(&self, id: impl Into<ModelId>) -> Option<&Model> {
102 self.models.get(&id.into())
103 }
104
105 pub fn model(&self, id: impl Into<ModelId>) -> &Model {
111 self.models.get(&id.into()).expect("invalid model ID")
112 }
113
114 pub fn resolve<'a>(
126 &'a self,
127 root: &'a Model,
128 projection: &stmt::Projection,
129 ) -> Option<Resolved<'a>> {
130 let [first, rest @ ..] = projection.as_slice() else {
131 return None;
132 };
133
134 let mut current_field = root.as_root_unwrap().fields.get(*first)?;
136
137 let mut steps = rest.iter();
140 while let Some(step) = steps.next() {
141 match ¤t_field.ty {
142 FieldTy::Primitive(..) => {
143 return None;
145 }
146 FieldTy::Embedded(embedded) => {
147 let target = self.model(embedded.target);
148 match target {
149 Model::EmbeddedStruct(s) => {
150 current_field = s.fields.get(*step)?;
151 }
152 Model::EmbeddedEnum(e) => {
153 let variant = e.variants.get(*step)?;
154
155 if let Some(field_step) = steps.next() {
157 current_field = e.fields.get(*field_step)?;
159 } else {
160 return Some(Resolved::Variant(variant));
162 }
163 }
164 _ => return None,
165 }
166 }
167 FieldTy::BelongsTo(belongs_to) => {
168 current_field = belongs_to.target(self).as_root_unwrap().fields.get(*step)?;
169 }
170 FieldTy::HasMany(has_many) => {
171 current_field = has_many.target(self).as_root_unwrap().fields.get(*step)?;
172 }
173 FieldTy::HasOne(has_one) => {
174 current_field = has_one.target(self).as_root_unwrap().fields.get(*step)?;
175 }
176 };
177 }
178
179 Some(Resolved::Field(current_field))
180 }
181
182 pub fn resolve_field<'a>(
187 &'a self,
188 root: &'a Model,
189 projection: &stmt::Projection,
190 ) -> Option<&'a Field> {
191 match self.resolve(root, projection) {
192 Some(Resolved::Field(field)) => Some(field),
193 _ => None,
194 }
195 }
196
197 pub fn resolve_field_path<'a>(&'a self, path: &stmt::Path) -> Option<&'a Field> {
200 let model = self.model(path.root.as_model_unwrap());
201 self.resolve_field(model, &path.projection)
202 }
203}
204
205impl Builder {
206 pub(crate) fn from_macro(models: impl IntoIterator<Item = Model>) -> Result<Schema> {
207 let mut builder = Self { ..Self::default() };
208
209 for model in models {
210 builder.models.insert(model.id(), model);
211 }
212
213 builder.process_models()?;
214 builder.into_schema()
215 }
216
217 fn into_schema(self) -> Result<Schema> {
218 Ok(Schema {
219 models: self.models,
220 })
221 }
222
223 fn process_models(&mut self) -> Result<()> {
224 self.link_relations()?;
227
228 Ok(())
229 }
230
231 fn link_relations(&mut self) -> crate::Result<()> {
233 for curr in 0..self.models.len() {
241 if self.models[curr].is_embedded() {
242 continue;
243 }
244 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
245 let model = &self.models[curr];
246 let src = model.id();
247 let field = &model.as_root_unwrap().fields[index];
248
249 if let FieldTy::HasMany(has_many) = &field.ty {
250 let target = has_many.target;
251 let field_name = field.name.app_unwrap().to_string();
252 let pair = self.find_has_many_pair(src, target, &field_name)?;
253 self.models[curr].as_root_mut_unwrap().fields[index]
254 .ty
255 .as_has_many_mut_unwrap()
256 .pair = pair;
257 }
258 }
259 }
260
261 for curr in 0..self.models.len() {
263 if self.models[curr].is_embedded() {
264 continue;
265 }
266 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
267 let model = &self.models[curr];
268 let src = model.id();
269 let field = &model.as_root_unwrap().fields[index];
270
271 match &field.ty {
272 FieldTy::HasOne(has_one) => {
273 let target = has_one.target;
274 let field_name = field.name.app_unwrap().to_string();
275 let pair = match self.find_belongs_to_pair(src, target, &field_name)? {
276 Some(pair) => pair,
277 None => {
278 return Err(crate::Error::invalid_schema(format!(
279 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
280 self.models[curr].name().upper_camel_case(),
281 field_name,
282 )));
283 }
284 };
285
286 self.models[curr].as_root_mut_unwrap().fields[index]
287 .ty
288 .as_has_one_mut_unwrap()
289 .pair = pair;
290 }
291 FieldTy::BelongsTo(belongs_to) => {
292 assert!(!belongs_to.foreign_key.is_placeholder());
293 continue;
294 }
295 _ => {}
296 }
297 }
298 }
299
300 for curr in 0..self.models.len() {
302 if self.models[curr].is_embedded() {
303 continue;
304 }
305 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
306 let model = &self.models[curr];
307 let field_id = model.as_root_unwrap().fields[index].id;
308
309 let pair = match &self.models[curr].as_root_unwrap().fields[index].ty {
310 FieldTy::BelongsTo(belongs_to) => {
311 let mut pair = None;
312 let target = match self.models.get_index_of(&belongs_to.target) {
313 Some(target) => target,
314 None => {
315 let model = &self.models[curr];
316 return Err(crate::Error::invalid_schema(format!(
317 "field `{}::{}` references a model that was not registered \
318 with the schema; did you forget to register it with `Db::builder()`?",
319 model.name().upper_camel_case(),
320 model.as_root_unwrap().fields[index].name(),
321 )));
322 }
323 };
324
325 for target_index in 0..self.models[target].as_root_unwrap().fields.len() {
326 pair = match &self.models[target].as_root_unwrap().fields[target_index]
327 .ty
328 {
329 FieldTy::HasMany(has_many) if has_many.pair == field_id => {
330 assert!(pair.is_none());
331 Some(
332 self.models[target].as_root_unwrap().fields[target_index]
333 .id,
334 )
335 }
336 FieldTy::HasOne(has_one) if has_one.pair == field_id => {
337 assert!(pair.is_none());
338 Some(
339 self.models[target].as_root_unwrap().fields[target_index]
340 .id,
341 )
342 }
343 _ => continue,
344 }
345 }
346
347 if pair.is_none() {
348 continue;
349 }
350
351 pair
352 }
353 _ => continue,
354 };
355
356 self.models[curr].as_root_mut_unwrap().fields[index]
357 .ty
358 .as_belongs_to_mut_unwrap()
359 .pair = pair;
360 }
361 }
362
363 Ok(())
364 }
365
366 fn find_belongs_to_pair(
367 &self,
368 src: ModelId,
369 target: ModelId,
370 field_name: &str,
371 ) -> crate::Result<Option<FieldId>> {
372 let src_model = &self.models[&src];
373
374 let target = match self.models.get(&target) {
375 Some(target) => target,
376 None => {
377 return Err(crate::Error::invalid_schema(format!(
378 "field `{}::{}` references a model that was not registered with the schema; \
379 did you forget to register it with `Db::builder()`?",
380 src_model.name().upper_camel_case(),
381 field_name,
382 )));
383 }
384 };
385
386 let belongs_to: Vec<_> = target
388 .as_root_unwrap()
389 .fields
390 .iter()
391 .filter(|field| match &field.ty {
392 FieldTy::BelongsTo(rel) => rel.target == src,
393 _ => false,
394 })
395 .collect();
396
397 match &belongs_to[..] {
398 [field] => Ok(Some(field.id)),
399 [] => Ok(None),
400 _ => Err(crate::Error::invalid_schema(format!(
401 "model `{}` has more than one `BelongsTo` relation targeting `{}`",
402 target.name().upper_camel_case(),
403 src_model.name().upper_camel_case(),
404 ))),
405 }
406 }
407
408 fn find_has_many_pair(
409 &mut self,
410 src: ModelId,
411 target: ModelId,
412 field_name: &str,
413 ) -> crate::Result<FieldId> {
414 if let Some(field_id) = self.find_belongs_to_pair(src, target, field_name)? {
415 return Ok(field_id);
416 }
417
418 Err(crate::Error::invalid_schema(format!(
419 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
420 self.models[&src].name().upper_camel_case(),
421 field_name,
422 )))
423 }
424}