Skip to main content

teaql_runtime/
registry.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use teaql_core::{
5    CompactRow, DeleteCommand, Entity, EntityDescriptor, EntityDescriptorStore, EntityError,
6    IdentifiableEntity, InsertCommand, RecoverCommand, SelectQuery, TeaqlEntity, UpdateCommand,
7};
8
9use crate::{
10    Checker, EntityGraphBuilder, EntityRuntimeState, GraphNode, InMemoryCheckerRegistry,
11    InMemoryRawAuditEventSink, Language, RawAuditEventSink, RuntimeError, UserContext,
12};
13
14type CompactEntityGraphDecoder =
15    fn(CompactRow, &EntityRuntimeState, &mut EntityGraphBuilder) -> Result<(), EntityError>;
16type CompactEntityGraphBatchDecoder =
17    fn(Vec<CompactRow>, &EntityRuntimeState, &mut EntityGraphBuilder) -> Result<(), EntityError>;
18type CompactEntityGraphListDecoder = fn(
19    Vec<CompactRow>,
20    &EntityRuntimeState,
21    &mut EntityGraphBuilder,
22    &str,
23    u64,
24    &str,
25) -> Result<(), EntityError>;
26
27#[derive(Default, Clone)]
28pub struct InMemoryEntityGraphDecoderRegistry {
29    compact_decoders: BTreeMap<String, CompactEntityGraphDecoder>,
30    compact_batch_decoders: BTreeMap<String, CompactEntityGraphBatchDecoder>,
31    compact_list_decoders: BTreeMap<String, CompactEntityGraphListDecoder>,
32    compact_option_decoders: BTreeMap<String, CompactEntityGraphListDecoder>,
33}
34
35impl InMemoryEntityGraphDecoderRegistry {
36    pub fn contains(&self, entity: &str) -> bool {
37        self.compact_decoders.contains_key(entity)
38    }
39
40    pub fn register<T>(&mut self)
41    where
42        T: Entity + IdentifiableEntity + Send + Sync + 'static,
43    {
44        fn decode_compact<T>(
45            row: CompactRow,
46            root: &EntityRuntimeState,
47            graph: &mut EntityGraphBuilder,
48        ) -> Result<(), EntityError>
49        where
50            T: Entity + IdentifiableEntity + Send + Sync + 'static,
51        {
52            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
53            let entity = T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any)?;
54            let id = entity.id_value().try_u64().ok_or_else(|| {
55                EntityError::new(T::ENTITY_NAME, "identity graph requires a u64 entity id")
56            })?;
57            graph.install(id, entity);
58            Ok(())
59        }
60
61        fn decode_compact_list<T>(
62            rows: Vec<CompactRow>,
63            root: &EntityRuntimeState,
64            graph: &mut EntityGraphBuilder,
65            owner_entity: &str,
66            owner_id: u64,
67            relation: &str,
68        ) -> Result<(), EntityError>
69        where
70            T: Entity + IdentifiableEntity + Send + Sync + 'static,
71        {
72            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
73            // `collect::<Result<Vec<_>, _>>()` cannot retain the exact size hint
74            // through the fallible adapter. For large generated entities that
75            // grows 4 -> 8 -> 16 even when the relation cardinality is already
76            // known. Reserve the exact row count and decode directly into the
77            // final SmartList allocation.
78            let mut entities = Vec::with_capacity(rows.len());
79            for row in rows {
80                entities.push(T::from_compact_row_with_context(
81                    row,
82                    &graph_root as &dyn std::any::Any,
83                )?);
84            }
85            graph.install_relation_list(
86                owner_entity,
87                owner_id,
88                relation,
89                teaql_core::SmartList::new(entities),
90            );
91            Ok(())
92        }
93
94        fn decode_compact_batch<T>(
95            rows: Vec<CompactRow>,
96            root: &EntityRuntimeState,
97            graph: &mut EntityGraphBuilder,
98        ) -> Result<(), EntityError>
99        where
100            T: Entity + IdentifiableEntity + Send + Sync + 'static,
101        {
102            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
103            for row in rows {
104                let entity =
105                    T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any)?;
106                let id = entity.id_value().try_u64().ok_or_else(|| {
107                    EntityError::new(T::ENTITY_NAME, "identity graph requires a u64 entity id")
108                })?;
109                graph.install(id, entity);
110            }
111            Ok(())
112        }
113
114        fn decode_compact_option<T>(
115            rows: Vec<CompactRow>,
116            root: &EntityRuntimeState,
117            graph: &mut EntityGraphBuilder,
118            owner_entity: &str,
119            owner_id: u64,
120            relation: &str,
121        ) -> Result<(), EntityError>
122        where
123            T: Entity + IdentifiableEntity + Send + Sync + 'static,
124        {
125            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
126            let value = rows
127                .into_iter()
128                .next()
129                .map(|row| T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any))
130                .transpose()?;
131            graph.install_relation_option(owner_entity, owner_id, relation, value);
132            Ok(())
133        }
134
135        self.compact_decoders
136            .insert(T::ENTITY_NAME.to_owned(), decode_compact::<T>);
137        self.compact_batch_decoders
138            .insert(T::ENTITY_NAME.to_owned(), decode_compact_batch::<T>);
139        self.compact_list_decoders
140            .insert(T::ENTITY_NAME.to_owned(), decode_compact_list::<T>);
141        self.compact_option_decoders
142            .insert(T::ENTITY_NAME.to_owned(), decode_compact_option::<T>);
143    }
144
145    pub fn decode_compact(
146        &self,
147        entity: &str,
148        row: CompactRow,
149        root: &EntityRuntimeState,
150        graph: &mut EntityGraphBuilder,
151    ) -> Result<(), EntityError> {
152        self.compact_decoders.get(entity).ok_or_else(|| {
153            EntityError::new(
154                entity,
155                "entity has no compact identity graph decoder in RuntimeModule",
156            )
157        })?(row, root, graph)
158    }
159
160    pub fn decode_compact_list(
161        &self,
162        entity: &str,
163        rows: Vec<CompactRow>,
164        root: &EntityRuntimeState,
165        graph: &mut EntityGraphBuilder,
166        owner_entity: &str,
167        owner_id: u64,
168        relation: &str,
169    ) -> Result<(), EntityError> {
170        self.compact_list_decoders.get(entity).ok_or_else(|| {
171            EntityError::new(
172                entity,
173                "entity has no compact identity graph list decoder in RuntimeModule",
174            )
175        })?(rows, root, graph, owner_entity, owner_id, relation)
176    }
177
178    pub fn decode_compact_batch(
179        &self,
180        entity: &str,
181        rows: Vec<CompactRow>,
182        root: &EntityRuntimeState,
183        graph: &mut EntityGraphBuilder,
184    ) -> Result<(), EntityError> {
185        self.compact_batch_decoders.get(entity).ok_or_else(|| {
186            EntityError::new(
187                entity,
188                "entity has no compact identity graph batch decoder in RuntimeModule",
189            )
190        })?(rows, root, graph)
191    }
192
193    pub fn decode_compact_option(
194        &self,
195        entity: &str,
196        rows: Vec<CompactRow>,
197        root: &EntityRuntimeState,
198        graph: &mut EntityGraphBuilder,
199        owner_entity: &str,
200        owner_id: u64,
201        relation: &str,
202    ) -> Result<(), EntityError> {
203        self.compact_option_decoders.get(entity).ok_or_else(|| {
204            EntityError::new(
205                entity,
206                "entity has no compact identity graph option decoder in RuntimeModule",
207            )
208        })?(rows, root, graph, owner_entity, owner_id, relation)
209    }
210}
211
212pub trait MetadataStore: Send + Sync {
213    fn entity(&self, name: &str) -> Option<&EntityDescriptor>;
214    fn all_entities(&self) -> Vec<&EntityDescriptor>;
215    fn record_metadata_log(&self, _metadata: &teaql_data_service::ExecutionMetadata) {}
216    fn capture_query_debug(&self) -> bool {
217        true
218    }
219    fn capture_execution_metadata(&self) -> bool {
220        true
221    }
222}
223
224pub trait EntityRegistry: Send + Sync {
225    fn contains(&self, entity: &str) -> bool;
226}
227
228pub trait RequestPolicy: Send + Sync {
229    fn enforce_select(
230        &self,
231        _ctx: &UserContext,
232        _query: &mut SelectQuery,
233    ) -> Result<(), RuntimeError> {
234        Ok(())
235    }
236
237    fn enforce_insert(
238        &self,
239        _ctx: &UserContext,
240        _command: &mut InsertCommand,
241    ) -> Result<(), RuntimeError> {
242        Ok(())
243    }
244
245    fn enforce_update(
246        &self,
247        _ctx: &UserContext,
248        _command: &mut UpdateCommand,
249    ) -> Result<(), RuntimeError> {
250        Ok(())
251    }
252
253    fn enforce_delete(
254        &self,
255        _ctx: &UserContext,
256        _command: &mut DeleteCommand,
257    ) -> Result<(), RuntimeError> {
258        Ok(())
259    }
260
261    fn enforce_recover(
262        &self,
263        _ctx: &UserContext,
264        _command: &mut RecoverCommand,
265    ) -> Result<(), RuntimeError> {
266        Ok(())
267    }
268}
269
270pub trait EntityDataServiceBehavior: Send + Sync {
271    fn before_select(
272        &self,
273        _ctx: &UserContext,
274        _query: &mut SelectQuery,
275    ) -> Result<(), RuntimeError> {
276        Ok(())
277    }
278
279    fn before_insert(
280        &self,
281        _ctx: &UserContext,
282        _command: &mut InsertCommand,
283    ) -> Result<(), RuntimeError> {
284        Ok(())
285    }
286
287    fn before_update(
288        &self,
289        _ctx: &UserContext,
290        _command: &mut UpdateCommand,
291    ) -> Result<(), RuntimeError> {
292        Ok(())
293    }
294
295    fn before_delete(
296        &self,
297        _ctx: &UserContext,
298        _command: &mut DeleteCommand,
299    ) -> Result<(), RuntimeError> {
300        Ok(())
301    }
302
303    fn before_recover(
304        &self,
305        _ctx: &UserContext,
306        _command: &mut RecoverCommand,
307    ) -> Result<(), RuntimeError> {
308        Ok(())
309    }
310
311    fn relation_loads(&self, _ctx: &UserContext) -> Vec<String> {
312        Vec::new()
313    }
314}
315
316pub trait EntityDataServiceBehaviorRegistry: Send + Sync {
317    fn behavior(&self, entity: &str) -> Option<Arc<dyn EntityDataServiceBehavior>>;
318}
319
320#[derive(Debug, Default, Clone)]
321pub struct InMemoryMetadataStore {
322    entities: BTreeMap<String, EntityDescriptor>,
323}
324
325impl InMemoryMetadataStore {
326    pub fn new() -> Self {
327        Self::default()
328    }
329
330    pub fn register(&mut self, entity: EntityDescriptor) {
331        self.entities.insert(entity.name.clone(), entity);
332    }
333
334    pub fn with_entity(mut self, entity: EntityDescriptor) -> Self {
335        self.register(entity);
336        self
337    }
338}
339
340impl MetadataStore for InMemoryMetadataStore {
341    fn entity(&self, name: &str) -> Option<&EntityDescriptor> {
342        self.entities.get(name)
343    }
344
345    fn all_entities(&self) -> Vec<&EntityDescriptor> {
346        self.entities.values().collect()
347    }
348}
349
350impl teaql_data_service::SchemaProvider for InMemoryMetadataStore {
351    fn get_entity(&self, name: &str) -> Option<std::sync::Arc<teaql_core::EntityDescriptor>> {
352        self.entities
353            .get(name)
354            .map(|e| std::sync::Arc::new(e.clone()))
355    }
356}
357
358impl EntityDescriptorStore for InMemoryMetadataStore {
359    fn register_descriptor(&mut self, descriptor: EntityDescriptor) {
360        self.register(descriptor);
361    }
362}
363
364#[derive(Debug, Default, Clone)]
365pub struct InMemoryEntityRegistry {
366    entities: BTreeMap<String, String>,
367}
368
369impl InMemoryEntityRegistry {
370    pub fn new() -> Self {
371        Self::default()
372    }
373
374    pub fn register(&mut self, entity: impl Into<String>) {
375        let entity = entity.into();
376        self.entities.insert(entity.clone(), entity);
377    }
378
379    pub fn with_entity(mut self, entity: impl Into<String>) -> Self {
380        self.register(entity);
381        self
382    }
383}
384
385impl EntityRegistry for InMemoryEntityRegistry {
386    fn contains(&self, entity: &str) -> bool {
387        self.entities.contains_key(entity)
388    }
389}
390
391#[derive(Default, Clone)]
392pub struct InMemoryEntityDataServiceBehaviorRegistry {
393    behaviors: BTreeMap<String, Arc<dyn EntityDataServiceBehavior>>,
394}
395
396impl InMemoryEntityDataServiceBehaviorRegistry {
397    pub fn new() -> Self {
398        Self::default()
399    }
400
401    pub fn register(
402        &mut self,
403        entity: impl Into<String>,
404        behavior: impl EntityDataServiceBehavior + 'static,
405    ) {
406        self.behaviors.insert(entity.into(), Arc::new(behavior));
407    }
408
409    pub fn with_behavior(
410        mut self,
411        entity: impl Into<String>,
412        behavior: impl EntityDataServiceBehavior + 'static,
413    ) -> Self {
414        self.register(entity, behavior);
415        self
416    }
417}
418
419impl EntityDataServiceBehaviorRegistry for InMemoryEntityDataServiceBehaviorRegistry {
420    fn behavior(&self, entity: &str) -> Option<Arc<dyn EntityDataServiceBehavior>> {
421        self.behaviors.get(entity).cloned()
422    }
423}
424
425#[derive(Default, Clone)]
426pub struct RuntimeModule {
427    pub metadata: InMemoryMetadataStore,
428    entity_registry: InMemoryEntityRegistry,
429    behaviors: InMemoryEntityDataServiceBehaviorRegistry,
430    checkers: InMemoryCheckerRegistry,
431    event_sinks: InMemoryRawAuditEventSink,
432    language: Option<Language>,
433    initial_graphs: Vec<GraphNode>,
434    root_graphs: Vec<GraphNode>,
435    graph_decoders: InMemoryEntityGraphDecoderRegistry,
436}
437
438impl RuntimeModule {
439    pub fn new() -> Self {
440        Self::default()
441    }
442
443    pub fn entity<T>(mut self) -> Self
444    where
445        T: Entity + IdentifiableEntity + Send + Sync + 'static,
446    {
447        let descriptor = T::entity_descriptor();
448        self.entity_registry.register(descriptor.name.clone());
449        self.metadata.register(descriptor);
450        self.graph_decoders.register::<T>();
451        self
452    }
453
454    pub fn entity_with_behavior<T, B>(mut self, behavior: B) -> Self
455    where
456        T: Entity + IdentifiableEntity + Send + Sync + 'static,
457        B: EntityDataServiceBehavior + 'static,
458    {
459        let descriptor = T::entity_descriptor();
460        let entity_name = descriptor.name.clone();
461        self.entity_registry.register(entity_name.clone());
462        self.metadata.register(descriptor);
463        self.behaviors.register(entity_name, behavior);
464        self.graph_decoders.register::<T>();
465        self
466    }
467
468    pub fn descriptor(mut self, descriptor: EntityDescriptor) -> Self {
469        self.entity_registry.register(descriptor.name.clone());
470        self.metadata.register(descriptor);
471        self
472    }
473
474    pub fn behavior(
475        mut self,
476        entity: impl Into<String>,
477        behavior: impl EntityDataServiceBehavior + 'static,
478    ) -> Self {
479        self.behaviors.register(entity, behavior);
480        self
481    }
482
483    pub fn checker(mut self, checker: impl Checker + 'static) -> Self {
484        self.checkers.register(checker);
485        self
486    }
487
488    pub fn event_sink(mut self, sink: impl RawAuditEventSink + 'static) -> Self {
489        self.event_sinks.register(sink);
490        self
491    }
492
493    pub fn language(mut self, language: Language) -> Self {
494        self.language = Some(language);
495        self
496    }
497
498    pub fn initial_graph(mut self, graph: GraphNode) -> Self {
499        self.initial_graphs.push(graph);
500        self
501    }
502
503    pub fn initial_graphs(mut self, graphs: impl IntoIterator<Item = GraphNode>) -> Self {
504        self.initial_graphs.extend(graphs);
505        self
506    }
507
508    /// Register create-if-absent root data. Unlike constant initial graphs,
509    /// existing root rows are never reconciled from module defaults.
510    pub fn root_graph(mut self, graph: GraphNode) -> Self {
511        self.root_graphs.push(graph);
512        self
513    }
514
515    pub fn root_graphs(mut self, graphs: impl IntoIterator<Item = GraphNode>) -> Self {
516        self.root_graphs.extend(graphs);
517        self
518    }
519
520    pub fn apply_to(self, context: &mut UserContext) {
521        context.set_metadata(self.metadata);
522        context.set_entity_registry(self.entity_registry);
523        context.set_entity_data_service_behavior_registry(self.behaviors);
524        context.set_checker_registry(self.checkers);
525        context.set_event_sink(self.event_sinks);
526        context.set_initial_graphs(self.initial_graphs);
527        context.set_root_graphs(self.root_graphs);
528        context.set_entity_graph_decoder_registry(self.graph_decoders);
529        if let Some(language) = self.language {
530            context.set_language(language);
531        }
532    }
533
534    pub fn into_context(self) -> UserContext {
535        let mut context = UserContext::new();
536        self.apply_to(&mut context);
537        context
538    }
539}
540
541#[macro_export]
542macro_rules! module {
543    ($($entity:ty $(=> $behavior:expr)?),+ $(,)?) => {{
544        let module = $crate::RuntimeModule::new();
545        $crate::module!(@build module; $($entity $(=> $behavior)?),+)
546    }};
547
548    (@build $module:expr; $entity:ty => $behavior:expr, $($rest:tt)*) => {{
549        let module = $module.entity_with_behavior::<$entity, _>($behavior);
550        $crate::module!(@build module; $($rest)*)
551    }};
552
553    (@build $module:expr; $entity:ty, $($rest:tt)*) => {{
554        let module = $module.entity::<$entity>();
555        $crate::module!(@build module; $($rest)*)
556    }};
557
558    (@build $module:expr; $entity:ty => $behavior:expr) => {
559        $module.entity_with_behavior::<$entity, _>($behavior)
560    };
561
562    (@build $module:expr; $entity:ty) => {
563        $module.entity::<$entity>()
564    };
565}