1#![deny(missing_docs)]
2
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use type_bridge_contract::id::is_canonical_thing_iid;
7use type_bridge_orm::manager::DynamicEntityManager;
8use type_bridge_orm::session::backend::TxType;
9
10use crate::__codegen::{CompleteModel, EntityModel, HydrationCapability, SubtypeRootModel};
11use crate::entity_codec::{
12 hydrate_entity, lower_entity_create, map_validation_error, resolve_discovered_entity,
13 resolve_entity_authority,
14};
15use crate::error::{Error, ModelValidationPhase};
16use crate::schema::Schema;
17use crate::{Database, Result};
18
19#[cfg(test)]
20mod tests;
21
22fn invalid_iid() -> Error {
23 Error::model_validation(
24 ModelValidationPhase::Input,
25 "invalid_iid",
26 vec!["iid".into()],
27 "IID is not canonical",
28 None,
29 )
30}
31
32fn schema_not_bound() -> Error {
33 Error::model_validation(
34 ModelValidationPhase::Input,
35 "schema_not_bound",
36 vec![],
37 "database is not schema-bound",
38 None,
39 )
40}
41
42pub(crate) async fn rehydrate_written_entity<M>(
45 manager: &DynamicEntityManager<'_>,
46 iid: &str,
47 id: &type_bridge_contract::id::TypeId,
48 installed: &type_bridge_orm::InstalledRuntimeProjection,
49) -> Result<M>
50where
51 M: crate::__codegen::CompleteModel,
52{
53 let row = manager
54 .get_by_iid_exact(iid)
55 .await
56 .map_err(Error::from_orm)?
57 .ok_or_else(|| {
58 Error::model_validation(
59 ModelValidationPhase::Hydration,
60 "missing_post_write_row",
61 vec!["iid".into()],
62 "written entity was not returned",
63 None,
64 )
65 })?;
66 let hydrated = hydrate_entity(row, id, installed)?;
67 M::materialize(&hydrated, &HydrationCapability::new())
68 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
69}
70
71impl<S, M> EntitySubtypeManager<'_, S, M>
72where
73 S: Schema,
74 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
75{
76 pub async fn get_by_iid(&self, _iid: &str) -> Result<Option<M::Subtypes>> {
79 if !is_canonical_thing_iid(_iid) {
80 return Err(invalid_iid());
81 }
82 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
83 let (_id, descriptor) = resolve_entity_authority(
84 M::TYPE_ID_JSON,
85 installed,
86 ModelValidationPhase::Input,
87 false,
88 )?;
89 let tx = self
90 .db
91 .inner_orm()
92 .transaction_context(TxType::Read)
93 .await
94 .map_err(Error::from_orm)?;
95 let manager =
96 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
97 let identity = match manager.discover_by_iid(_iid).await {
98 Ok(v) => v,
99 Err(e) => {
100 let _ = tx.close().await;
101 return Err(Error::from_orm(e));
102 }
103 };
104 let out = match identity {
105 None => None,
106 Some(identity) => {
107 let (child_id, child_descriptor) =
108 match resolve_discovered_entity(&identity.type_name, installed) {
109 Ok(v) => v,
110 Err(e) => {
111 let _ = tx.close().await;
112 return Err(e);
113 }
114 };
115 let child = DynamicEntityManager::with_canonical_transaction(
116 tx.clone(),
117 Arc::new(child_descriptor),
118 );
119 let row = match child.get_by_iid_exact(&identity.iid).await {
120 Ok(Some(v)) => v,
121 Ok(None) => {
122 let _ = tx.close().await;
123 return Err(Error::model_validation(
124 ModelValidationPhase::Hydration,
125 "missing_concrete_row",
126 vec!["iid".into()],
127 "discovered entity row is missing",
128 None,
129 ));
130 }
131 Err(e) => {
132 let _ = tx.close().await;
133 return Err(Error::from_orm(e));
134 }
135 };
136 let h = match hydrate_entity(row, &child_id, installed) {
137 Ok(v) => v,
138 Err(e) => {
139 let _ = tx.close().await;
140 return Err(e);
141 }
142 };
143 Some(
144 match M::__tb_dispatch_subtype(&h, &HydrationCapability::new()) {
145 Ok(v) => v,
146 Err(e) => {
147 let _ = tx.close().await;
148 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
149 }
150 },
151 )
152 }
153 };
154 tx.close().await.map_err(Error::from_orm)?;
155 Ok(out)
156 }
157 pub async fn all(&self) -> Result<Vec<M::Subtypes>> {
161 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
162 let (_id, descriptor) = resolve_entity_authority(
163 M::TYPE_ID_JSON,
164 installed,
165 ModelValidationPhase::Input,
166 false,
167 )?;
168 let tx = self
169 .db
170 .inner_orm()
171 .transaction_context(TxType::Read)
172 .await
173 .map_err(Error::from_orm)?;
174 let manager =
175 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
176 let identities = match manager.discover_all().await {
177 Ok(v) => v,
178 Err(e) => {
179 let _ = tx.close().await;
180 return Err(Error::from_orm(e));
181 }
182 };
183 let mut out = Vec::with_capacity(identities.len());
184 for identity in identities {
185 let type_json = identity.type_name;
186 let (child_id, child_descriptor) =
187 match resolve_discovered_entity(&type_json, installed) {
188 Ok(v) => v,
189 Err(e) => {
190 let _ = tx.close().await;
191 return Err(e);
192 }
193 };
194 let child = DynamicEntityManager::with_canonical_transaction(
195 tx.clone(),
196 Arc::new(child_descriptor),
197 );
198 let row = match child.get_by_iid_exact(&identity.iid).await {
199 Ok(Some(v)) => v,
200 Ok(None) => {
201 let _ = tx.close().await;
202 return Err(Error::model_validation(
203 ModelValidationPhase::Hydration,
204 "missing_concrete_row",
205 vec!["iid".into()],
206 "discovered entity row is missing",
207 None,
208 ));
209 }
210 Err(e) => {
211 let _ = tx.close().await;
212 return Err(Error::from_orm(e));
213 }
214 };
215 let h = match hydrate_entity(row, &child_id, installed) {
216 Ok(v) => v,
217 Err(e) => {
218 let _ = tx.close().await;
219 return Err(e);
220 }
221 };
222 out.push(
223 match M::__tb_dispatch_subtype(&h, &HydrationCapability::new()) {
224 Ok(v) => v,
225 Err(e) => {
226 let _ = tx.close().await;
227 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
228 }
229 },
230 );
231 }
232 tx.close().await.map_err(Error::from_orm)?;
233 Ok(out)
234 }
235 pub async fn count(&self) -> Result<u64> {
237 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
238 let (_id, descriptor) = resolve_entity_authority(
239 M::TYPE_ID_JSON,
240 installed,
241 ModelValidationPhase::Input,
242 false,
243 )?;
244 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor))
245 .count()
246 .await
247 .map_err(Error::from_orm)
248 }
249}
250
251pub struct EntityManager<'db, S: Schema, M: EntityModel<Schema = S>> {
255 db: &'db Database<S>,
256 marker: PhantomData<M>,
257}
258
259pub struct EntitySubtypeManager<
264 'db,
265 S: Schema,
266 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
267> {
268 db: &'db Database<S>,
269 marker: PhantomData<M>,
270}
271
272impl<'db, S: Schema, M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>>
273 EntitySubtypeManager<'db, S, M>
274{
275 pub(crate) fn new(db: &'db Database<S>) -> Self {
276 Self {
277 db,
278 marker: PhantomData,
279 }
280 }
281}
282
283impl<'db, S, M> EntityManager<'db, S, M>
284where
285 S: Schema,
286 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
287{
288 pub fn subtypes(&self) -> EntitySubtypeManager<'db, S, M> {
291 EntitySubtypeManager::new(self.db)
292 }
293}
294
295impl<'db, S: Schema, M: EntityModel<Schema = S>> Copy for EntityManager<'db, S, M> {}
296impl<'db, S: Schema, M: EntityModel<Schema = S>> Clone for EntityManager<'db, S, M> {
297 fn clone(&self) -> Self {
298 *self
299 }
300}
301
302impl<S: Schema, M: EntityModel<Schema = S>> EntityManager<'_, S, M> {
303 pub(crate) fn new(db: &Database<S>) -> EntityManager<'_, S, M> {
304 EntityManager {
305 db,
306 marker: PhantomData,
307 }
308 }
309}
310
311impl<S, M> EntityManager<'_, S, M>
312where
313 S: Schema,
314 M: EntityModel<Schema = S> + CompleteModel,
315{
316 pub async fn insert(&self, input: M::Create) -> Result<M> {
319 self.write(input, false).await
320 }
321 pub async fn put(&self, input: M::Create) -> Result<M> {
326 self.write(input, true).await
327 }
328 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
331 if inputs.is_empty() {
332 return Ok(Vec::new());
333 }
334 self.write_many(inputs, false).await
335 }
336 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
340 if inputs.is_empty() {
341 return Ok(Vec::new());
342 }
343 self.write_many(inputs, true).await
344 }
345 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
349 if !is_canonical_thing_iid(iid) {
350 return Err(invalid_iid());
351 }
352 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
353 let (id, descriptor) = resolve_entity_authority(
354 M::TYPE_ID_JSON,
355 installed,
356 ModelValidationPhase::Input,
357 true,
358 )?;
359 let attrs = lower_entity_create(input, &id, installed)?;
360 let tx = self
361 .db
362 .inner_orm()
363 .transaction_context(TxType::Write)
364 .await
365 .map_err(Error::from_orm)?;
366 let manager = DynamicEntityManager::with_canonical_transaction(
367 tx.clone(),
368 Arc::new(descriptor.clone()),
369 );
370 if let Err(error) = manager.update_exact(iid, &attrs).await {
371 let _ = tx.rollback().await;
372 return Err(Error::from_orm(error));
373 }
374 let row = match manager.get_by_iid_exact(iid).await {
375 Ok(Some(row)) => row,
376 Ok(None) => {
377 let _ = tx.rollback().await;
378 return Err(Error::model_validation(
379 ModelValidationPhase::Hydration,
380 "missing_post_write_row",
381 vec!["iid".into()],
382 "updated entity was not returned",
383 None,
384 ));
385 }
386 Err(error) => {
387 let _ = tx.rollback().await;
388 return Err(Error::from_orm(error));
389 }
390 };
391 let hydrated = match hydrate_entity(row, &id, installed) {
392 Ok(value) => value,
393 Err(error) => {
394 let _ = tx.rollback().await;
395 return Err(error);
396 }
397 };
398 let value = match M::materialize(&hydrated, &HydrationCapability::new()) {
399 Ok(value) => value,
400 Err(error) => {
401 let _ = tx.rollback().await;
402 return Err(map_validation_error(error, ModelValidationPhase::Hydration));
403 }
404 };
405 tx.commit().await.map_err(Error::from_orm)?;
406 Ok(value)
407 }
408 pub async fn delete(&self, iid: &str) -> Result<()> {
410 if !is_canonical_thing_iid(iid) {
411 return Err(invalid_iid());
412 }
413 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
414 let (_id, descriptor) = resolve_entity_authority(
415 M::TYPE_ID_JSON,
416 installed,
417 ModelValidationPhase::Input,
418 true,
419 )?;
420 let tx = self
421 .db
422 .inner_orm()
423 .transaction_context(TxType::Write)
424 .await
425 .map_err(Error::from_orm)?;
426 let manager = DynamicEntityManager::with_canonical_transaction(
427 tx.clone(),
428 Arc::new(descriptor.clone()),
429 );
430 if let Err(error) = manager.delete_by_iid_exact(iid).await {
431 let _ = tx.rollback().await;
432 return Err(Error::from_orm(error));
433 }
434 tx.commit().await.map_err(Error::from_orm)
435 }
436 async fn write(&self, input: M::Create, put: bool) -> Result<M> {
437 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
438 let (id, descriptor) = resolve_entity_authority(
439 M::TYPE_ID_JSON,
440 installed,
441 ModelValidationPhase::Input,
442 true,
443 )?;
444 let attrs = lower_entity_create(input, &id, installed)?;
445 let tx = self
446 .db
447 .inner_orm()
448 .transaction_context(TxType::Write)
449 .await
450 .map_err(Error::from_orm)?;
451 let manager = DynamicEntityManager::with_canonical_transaction(
452 tx.clone(),
453 Arc::new(descriptor.clone()),
454 );
455 let iid = if put {
456 manager.put_exact(&attrs).await
457 } else {
458 manager.insert(&attrs).await
459 };
460 let iid = match iid {
461 Ok(iid) => iid,
462 Err(error) => {
463 let _ = tx.rollback().await;
464 return Err(Error::from_orm(error));
465 }
466 };
467 let row = match manager.get_by_iid_exact(&iid).await {
468 Ok(Some(row)) => row,
469 Ok(None) => {
470 let _ = tx.rollback().await;
471 return Err(Error::model_validation(
472 ModelValidationPhase::Hydration,
473 "missing_post_write_row",
474 vec!["iid".into()],
475 "written entity was not returned",
476 None,
477 ));
478 }
479 Err(error) => {
480 let _ = tx.rollback().await;
481 return Err(Error::from_orm(error));
482 }
483 };
484 let hydrated = match hydrate_entity(row, &id, installed) {
485 Ok(value) => value,
486 Err(error) => {
487 let _ = tx.rollback().await;
488 return Err(error);
489 }
490 };
491 let value = match M::materialize(&hydrated, &HydrationCapability::new()) {
492 Ok(value) => value,
493 Err(error) => {
494 let mapped = map_validation_error(error, ModelValidationPhase::Hydration);
495 let _ = tx.rollback().await;
496 return Err(mapped);
497 }
498 };
499 tx.commit().await.map_err(Error::from_orm)?;
500 Ok(value)
501 }
502
503 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
504 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
505 let (id, descriptor) = resolve_entity_authority(
506 M::TYPE_ID_JSON,
507 installed,
508 ModelValidationPhase::Input,
509 true,
510 )?;
511 let mut lowered = Vec::with_capacity(inputs.len());
512 for input in inputs {
513 lowered.push(lower_entity_create(input, &id, installed)?);
514 }
515 let tx = self
516 .db
517 .inner_orm()
518 .transaction_context(TxType::Write)
519 .await
520 .map_err(Error::from_orm)?;
521 let manager =
522 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
523 let iids = match if put {
524 manager.put_many_exact(&lowered).await
525 } else {
526 manager.insert_many(&lowered).await
527 } {
528 Ok(v) if v.len() == lowered.len() => v,
529 Ok(_) => {
530 let _ = tx.rollback().await;
531 return Err(Error::model_validation(
532 ModelValidationPhase::Hydration,
533 "iid_count_mismatch",
534 vec!["iid".into()],
535 "provider returned an unexpected IID count",
536 None,
537 ));
538 }
539 Err(e) => {
540 let _ = tx.rollback().await;
541 return Err(Error::from_orm(e));
542 }
543 };
544 let mut out = Vec::with_capacity(iids.len());
545 for iid in iids {
546 let row = match manager.get_by_iid_exact(&iid).await {
547 Ok(Some(r)) => r,
548 Ok(None) => {
549 let _ = tx.rollback().await;
550 return Err(Error::model_validation(
551 ModelValidationPhase::Hydration,
552 "missing_post_write_row",
553 vec!["iid".into()],
554 "written entity was not returned",
555 None,
556 ));
557 }
558 Err(e) => {
559 let _ = tx.rollback().await;
560 return Err(Error::from_orm(e));
561 }
562 };
563 let h = match hydrate_entity(row, &id, installed) {
564 Ok(v) => v,
565 Err(e) => {
566 let _ = tx.rollback().await;
567 return Err(e);
568 }
569 };
570 let value = match M::materialize(&h, &HydrationCapability::new()) {
571 Ok(v) => v,
572 Err(e) => {
573 let _ = tx.rollback().await;
574 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
575 }
576 };
577 out.push(value);
578 }
579 tx.commit().await.map_err(Error::from_orm)?;
580 Ok(out)
581 }
582 pub async fn count(&self) -> Result<u64> {
584 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
585 let (_id, descriptor) = resolve_entity_authority(
586 M::TYPE_ID_JSON,
587 installed,
588 ModelValidationPhase::Input,
589 true,
590 )?;
591 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
592 .count_exact()
593 .await
594 .map_err(Error::from_orm)
595 }
596
597 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
600 if !is_canonical_thing_iid(iid) {
601 return Err(invalid_iid());
602 }
603 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
604 let (id, descriptor) = resolve_entity_authority(
605 M::TYPE_ID_JSON,
606 installed,
607 ModelValidationPhase::Input,
608 true,
609 )?;
610 let row =
611 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
612 .get_by_iid_exact(iid)
613 .await
614 .map_err(Error::from_orm)?;
615 match row {
616 None => Ok(None),
617 Some(r) => {
618 let h = hydrate_entity(r, &id, installed)?;
619 let value = M::materialize(&h, &HydrationCapability::new())
620 .map_err(|e| map_validation_error(e, ModelValidationPhase::Hydration))?;
621 Ok(Some(value))
622 }
623 }
624 }
625
626 pub async fn all(&self) -> Result<Vec<M>> {
629 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
630 let (id, descriptor) = resolve_entity_authority(
631 M::TYPE_ID_JSON,
632 installed,
633 ModelValidationPhase::Input,
634 true,
635 )?;
636 let rows =
637 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
638 .all_exact()
639 .await
640 .map_err(Error::from_orm)?;
641 rows.into_iter()
642 .map(|r| {
643 let h = hydrate_entity(r, &id, installed)?;
644 M::materialize(&h, &HydrationCapability::new())
645 .map_err(|e| map_validation_error(e, ModelValidationPhase::Hydration))
646 })
647 .collect()
648 }
649}