1use super::{EnumVariant, Field, FieldId, FieldTy, HasKind, 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 HasKind::Direct(pair) = has_many.kind else {
252 continue;
253 };
254 let target = has_many.target;
255 let field_name = field.name.app_unwrap().to_string();
256 let pair = if pair.is_placeholder() {
257 self.find_has_many_pair(src, target, &field_name)?
258 } else {
259 self.validate_pair(src, target, &field_name, pair)?;
260 pair
261 };
262 self.models[curr].as_root_mut_unwrap().fields[index]
263 .ty
264 .as_has_many_mut_unwrap()
265 .kind = HasKind::Direct(pair);
266 }
267 }
268 }
269
270 for curr in 0..self.models.len() {
272 if self.models[curr].is_embedded() {
273 continue;
274 }
275 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
276 let model = &self.models[curr];
277 let src = model.id();
278 let field = &model.as_root_unwrap().fields[index];
279
280 match &field.ty {
281 FieldTy::HasOne(has_one) => {
282 let HasKind::Direct(pair) = has_one.kind else {
284 continue;
285 };
286 let target = has_one.target;
287 let field_name = field.name.app_unwrap().to_string();
288 let pair = if pair.is_placeholder() {
289 match self.find_belongs_to_pair(src, target, &field_name)? {
290 Some(pair) => pair,
291 None => {
292 return Err(crate::Error::invalid_schema(format!(
293 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
294 self.models[curr].name().upper_camel_case(),
295 field_name,
296 )));
297 }
298 }
299 } else {
300 self.validate_pair(src, target, &field_name, pair)?;
301 pair
302 };
303
304 self.models[curr].as_root_mut_unwrap().fields[index]
305 .ty
306 .as_has_one_mut_unwrap()
307 .kind = HasKind::Direct(pair);
308 }
309 FieldTy::BelongsTo(belongs_to) => {
310 assert!(!belongs_to.foreign_key.is_placeholder());
311 continue;
312 }
313 _ => {}
314 }
315 }
316 }
317
318 for curr in 0..self.models.len() {
320 if self.models[curr].is_embedded() {
321 continue;
322 }
323 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
324 let model = &self.models[curr];
325 let field_id = model.as_root_unwrap().fields[index].id;
326
327 let pair = match &self.models[curr].as_root_unwrap().fields[index].ty {
328 FieldTy::BelongsTo(belongs_to) => {
329 let mut pair = None;
330 let target = match self.models.get_index_of(&belongs_to.target) {
331 Some(target) => target,
332 None => {
333 let model = &self.models[curr];
334 return Err(crate::Error::invalid_schema(format!(
335 "field `{}::{}` references a model that was not registered \
336 with the schema; did you forget to register it with `Db::builder()`?",
337 model.name().upper_camel_case(),
338 model.as_root_unwrap().fields[index].name(),
339 )));
340 }
341 };
342
343 for target_index in 0..self.models[target].as_root_unwrap().fields.len() {
344 pair = match &self.models[target].as_root_unwrap().fields[target_index]
345 .ty
346 {
347 FieldTy::HasMany(has_many)
348 if has_many.kind.pair_id() == Some(field_id) =>
349 {
350 assert!(pair.is_none());
351 Some(
352 self.models[target].as_root_unwrap().fields[target_index]
353 .id,
354 )
355 }
356 FieldTy::HasOne(has_one)
357 if has_one.kind.pair_id() == Some(field_id) =>
358 {
359 assert!(pair.is_none());
360 Some(
361 self.models[target].as_root_unwrap().fields[target_index]
362 .id,
363 )
364 }
365 _ => continue,
366 }
367 }
368
369 if pair.is_none() {
370 continue;
371 }
372
373 pair
374 }
375 _ => continue,
376 };
377
378 self.models[curr].as_root_mut_unwrap().fields[index]
379 .ty
380 .as_belongs_to_mut_unwrap()
381 .pair = pair;
382 }
383 }
384
385 Ok(())
386 }
387
388 fn find_belongs_to_pair(
389 &self,
390 src: ModelId,
391 target: ModelId,
392 field_name: &str,
393 ) -> crate::Result<Option<FieldId>> {
394 let src_model = &self.models[&src];
395
396 let target = match self.models.get(&target) {
397 Some(target) => target,
398 None => {
399 return Err(crate::Error::invalid_schema(format!(
400 "field `{}::{}` references a model that was not registered with the schema; \
401 did you forget to register it with `Db::builder()`?",
402 src_model.name().upper_camel_case(),
403 field_name,
404 )));
405 }
406 };
407
408 let belongs_to: Vec<_> = target
410 .as_root_unwrap()
411 .fields
412 .iter()
413 .filter(|field| match &field.ty {
414 FieldTy::BelongsTo(rel) => rel.target == src,
415 _ => false,
416 })
417 .collect();
418
419 match &belongs_to[..] {
420 [field] => Ok(Some(field.id)),
421 [] => Ok(None),
422 _ => Err(crate::Error::invalid_schema(format!(
423 "model `{}` has more than one `BelongsTo` relation targeting `{}`; \
424 disambiguate by adding `pair = <field>` on the paired `has_many`/`has_one` \
425 field",
426 target.name().upper_camel_case(),
427 src_model.name().upper_camel_case(),
428 ))),
429 }
430 }
431
432 fn find_has_many_pair(
433 &mut self,
434 src: ModelId,
435 target: ModelId,
436 field_name: &str,
437 ) -> crate::Result<FieldId> {
438 if let Some(field_id) = self.find_belongs_to_pair(src, target, field_name)? {
439 return Ok(field_id);
440 }
441
442 Err(crate::Error::invalid_schema(format!(
443 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
444 self.models[&src].name().upper_camel_case(),
445 field_name,
446 )))
447 }
448
449 fn validate_pair(
453 &self,
454 src: ModelId,
455 target: ModelId,
456 field_name: &str,
457 pair: FieldId,
458 ) -> crate::Result<()> {
459 let src_model = &self.models[&src];
460
461 let target_model = match self.models.get(&target) {
462 Some(target) => target,
463 None => {
464 return Err(crate::Error::invalid_schema(format!(
465 "field `{}::{}` references a model that was not registered with the schema; \
466 did you forget to register it with `Db::builder()`?",
467 src_model.name().upper_camel_case(),
468 field_name,
469 )));
470 }
471 };
472
473 if pair.model != target {
474 return Err(crate::Error::invalid_schema(format!(
475 "field `{}::{}` specifies a `pair` on a model other than its target `{}`",
476 src_model.name().upper_camel_case(),
477 field_name,
478 target_model.name().upper_camel_case(),
479 )));
480 }
481
482 let paired = &target_model.as_root_unwrap().fields[pair.index];
483 match &paired.ty {
484 FieldTy::BelongsTo(rel) if rel.target == src => Ok(()),
485 _ => Err(crate::Error::invalid_schema(format!(
486 "field `{}::{}` specifies `pair = {}`, but `{}::{}` is not a `BelongsTo` \
487 targeting `{}`",
488 src_model.name().upper_camel_case(),
489 field_name,
490 paired.name.app_unwrap(),
491 target_model.name().upper_camel_case(),
492 paired.name.app_unwrap(),
493 src_model.name().upper_camel_case(),
494 ))),
495 }
496 }
497}