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