reactive_graph_graphql_schema/mutation/instances/
relation_instance.rs

1use std::sync::Arc;
2
3use async_graphql::*;
4use log::debug;
5use serde_json::json;
6use uuid::Uuid;
7
8use reactive_graph_behaviour_model_api::BehaviourTypeId;
9use reactive_graph_behaviour_service_api::RelationBehaviourManager;
10use reactive_graph_behaviour_service_api::RelationComponentBehaviourManager;
11use reactive_graph_graph::PropertyInstanceGetter;
12use reactive_graph_graph::PropertyInstanceSetter;
13use reactive_graph_graph::PropertyType;
14use reactive_graph_graph::RelationInstance;
15use reactive_graph_graph::RelationInstanceId;
16use reactive_graph_graph::RelationInstanceTypeId;
17use reactive_graph_reactive_model_api::ReactivePropertyContainer;
18use reactive_graph_reactive_service_api::ReactiveEntityManager;
19use reactive_graph_reactive_service_api::ReactiveRelationManager;
20use reactive_graph_type_system_api::RelationTypeManager;
21
22use crate::mutation::BehaviourTypeIdDefinition;
23use crate::mutation::ComponentTypeIdDefinition;
24use crate::mutation::GraphQLRelationInstanceId;
25use crate::mutation::RelationTypeIdDefinition;
26use crate::query::GraphQLPropertyInstance;
27use crate::query::GraphQLRelationInstance;
28
29#[derive(Default)]
30pub struct MutationRelationInstances;
31
32/// Mutation of relation instances.
33#[Object]
34impl MutationRelationInstances {
35    /// Creates a new relation instance with the given relation_instance_id.
36    ///
37    /// The relation instance id is the primary key of a relation instance and consists of the id of the
38    /// outbound entity instance, the name of the relation type and the id of the inbound
39    /// entity instance.
40    ///
41    /// The relation type must exist and the given type name is matched by a prefix search.
42    /// For example a given type name "default_connector--property_name--property_name" will match
43    /// as relation type "default_connector".
44    ///
45    /// Furthermore, the outbound and the inbound entity instance must exist.
46    ///
47    /// The given properties consists of a list of pairs of property name and property value.
48    /// If properties are not provided, default values will be used depending on the data type
49    /// of the property.
50    async fn create(
51        &self,
52        context: &Context<'_>,
53        #[graphql(desc = "Specifies the outbound id, the inbound id, the relation type and the instance_id.")] relation_instance_id: GraphQLRelationInstanceId,
54        #[graphql(desc = "Description of the entity instance.")] description: Option<String>,
55        #[graphql(desc = "Creates the relation instance with the given components.")] components: Option<Vec<ComponentTypeIdDefinition>>,
56        properties: Option<Vec<GraphQLPropertyInstance>>,
57    ) -> Result<GraphQLRelationInstance> {
58        let relation_type_manager = context.data::<Arc<dyn RelationTypeManager + Send + Sync>>()?;
59        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
60        let entity_instance_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
61
62        let relation_instance_ty = relation_instance_id.ty();
63        let relation_ty = relation_instance_ty.relation_type_id();
64
65        let relation_type = relation_type_manager
66            .get(&relation_ty)
67            .ok_or_else(|| Error::new(format!("Relation type {} does not exist!", &relation_ty)))?;
68
69        if !entity_instance_manager.has(relation_instance_id.outbound_id) {
70            return Err(Error::new(format!("Outbound entity {} does not exist!", relation_instance_id.outbound_id)));
71        }
72
73        if !entity_instance_manager.has(relation_instance_id.inbound_id) {
74            return Err(Error::new(format!("Inbound entity {} does not exist!", relation_instance_id.inbound_id)));
75        }
76
77        let properties = GraphQLPropertyInstance::to_property_instances_with_defaults(properties, relation_type.properties);
78
79        let relation_instance = RelationInstance::builder()
80            .outbound_id(relation_instance_id.outbound_id)
81            .ty(relation_instance_ty)
82            .inbound_id(relation_instance_id.inbound_id)
83            .description(description.unwrap_or_default())
84            .properties(properties)
85            .build();
86
87        let id: RelationInstanceId = relation_instance_id.into();
88
89        let relation_instance = relation_instance_manager.create_reactive_instance(relation_instance);
90
91        match relation_instance {
92            Ok(relation_instance) => {
93                if let Some(components) = components {
94                    for component in components {
95                        let component = component.into();
96                        // TODO: handle components which have not been added
97                        let _ = relation_instance_manager.add_component(&id, &component);
98                    }
99                }
100                Ok(relation_instance.into())
101            }
102            Err(e) => Err(e.into()),
103        }
104    }
105
106    /// Creates a connector from a property of the outbound entity instance to a property of the inbound entity instance.
107    ///
108    /// The type_name must match a relation type exactly.
109    #[allow(clippy::too_many_arguments)]
110    async fn create_connector(
111        &self,
112        context: &Context<'_>,
113        #[graphql(desc = "The id of the outbound entity instance")] outbound_id: Uuid,
114        #[graphql(desc = "The name of the property of the outbound entity instance")] outbound_property_name: String,
115        #[graphql(name = "type", desc = "The name of the connector relation type")] relation_ty: RelationTypeIdDefinition,
116        #[graphql(desc = "The id of the inbound entity instance")] inbound_id: Uuid,
117        #[graphql(desc = "The name of the property of the inbound entity instance")] inbound_property_name: String,
118        #[graphql(desc = "Creates the relation instance with the given components.")] components: Option<Vec<ComponentTypeIdDefinition>>,
119        #[graphql(desc = "The initial property values")] properties: Option<Vec<GraphQLPropertyInstance>>,
120    ) -> Result<GraphQLRelationInstance> {
121        let relation_type_manager = context.data::<Arc<dyn RelationTypeManager + Send + Sync>>()?;
122        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
123        let entity_instance_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
124
125        let relation_ty = relation_ty.into();
126
127        // Resolve the relation type or throw error
128        let relation_type = relation_type_manager
129            .get(&relation_ty)
130            .ok_or(Error::new(format!("Connector relation type {} does not exist!", &relation_ty)))?;
131
132        // The outbound entity instance must exist
133        if !entity_instance_manager.has(outbound_id) {
134            return Err(Error::new(format!("Outbound entity {outbound_id} does not exist!")));
135        }
136
137        // The inbound entity instance must exist
138        if !entity_instance_manager.has(inbound_id) {
139            return Err(Error::new(format!("Inbound entity {inbound_id} does not exist!")));
140        }
141
142        // The outbound entity instance's property must exist
143        if entity_instance_manager.get(outbound_id).map(|e| e.get(&outbound_property_name)).is_none() {
144            return Err(Error::new(format!("Outbound entity {outbound_id} has no property named {outbound_property_name}!")));
145        }
146
147        // The inbound entity instance's property must exist
148        if entity_instance_manager.get(inbound_id).map(|e| e.get(&inbound_property_name)).is_none() {
149            return Err(Error::new(format!("Inbound entity {inbound_id} has no property named {inbound_property_name}!")));
150        }
151
152        // Construct the instance_id because between two nodes only one edge with the same type
153        // can exist. Therefore, we construct a unique type which contains the names of the outbound
154        // property and the inbound property. This allows *exactly one* connector (of the given
155        // connector type) between the two properties.
156        let instance_id = format!("{outbound_property_name}__{inbound_property_name}");
157        let ty = RelationInstanceTypeId::new_unique_for_instance_id(relation_ty, instance_id);
158
159        // Construct a relation instance id using the outbound id, the type identifier (containing the
160        // previously generated instance_id) and the inbound id.
161        let id = RelationInstanceId::new(outbound_id, ty, inbound_id);
162
163        let properties = GraphQLPropertyInstance::to_property_instances_with_defaults(properties, relation_type.properties);
164        properties.insert("outbound_property_name".to_string(), json!(outbound_property_name));
165        properties.insert("inbound_property_name".to_string(), json!(inbound_property_name));
166        match relation_instance_manager.create_reactive_relation(&id, properties) {
167            Ok(relation_instance) => {
168                // If created successfully, add additional components
169                if let Some(components) = components {
170                    for component in components {
171                        // TODO: handle components which have not been added
172                        let _ = relation_instance_manager.add_component(&id, &component.into());
173                    }
174                }
175                Ok(relation_instance.into())
176            }
177            Err(e) => Err(Error::new(format!("Failed to create relation instance: {e:?}"))),
178        }
179    }
180
181    /// Updates the properties of the given relation instance by its relation instance id.
182    #[allow(clippy::too_many_arguments)]
183    async fn update(
184        &self,
185        context: &Context<'_>,
186        relation_instance_id: GraphQLRelationInstanceId,
187        #[graphql(desc = "Adds the components with the given name")] add_components: Option<Vec<ComponentTypeIdDefinition>>,
188        #[graphql(desc = "Removes the components with the given name")] remove_components: Option<Vec<ComponentTypeIdDefinition>>,
189        #[graphql(desc = "Updates the given properties")] properties: Option<Vec<GraphQLPropertyInstance>>,
190        #[graphql(desc = "Adds the given properties")] add_properties: Option<Vec<crate::mutation::PropertyTypeDefinition>>,
191        #[graphql(desc = "Removes the given properties")] remove_properties: Option<Vec<String>>,
192    ) -> Result<GraphQLRelationInstance> {
193        let relation_type_manager = context.data::<Arc<dyn RelationTypeManager + Send + Sync>>()?;
194        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
195        let entity_instance_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
196
197        if !entity_instance_manager.has(relation_instance_id.outbound_id) {
198            return Err(Error::new(format!("Outbound entity {} does not exist!", relation_instance_id.outbound_id)));
199        }
200
201        if !entity_instance_manager.has(relation_instance_id.inbound_id) {
202            return Err(Error::new(format!("Inbound entity {} does not exist!", relation_instance_id.inbound_id)));
203        }
204
205        let ty = relation_instance_id.ty();
206        let relation_ty = ty.relation_type_id();
207
208        if relation_type_manager.get(&relation_ty).is_none() {
209            return Err(Error::new(format!("Relation type {relation_instance_id} does not exist!")));
210        }
211
212        let id: RelationInstanceId = relation_instance_id.into();
213        let relation_instance = relation_instance_manager
214            .get(&id)
215            .ok_or_else(|| Error::new(format!("Relation instance {id} does not exist!")))?;
216
217        if let Some(components) = add_components {
218            for component in components {
219                // TODO: handle components which have not been added
220                let _ = relation_instance_manager.add_component(&id, &component.into());
221            }
222        }
223        if let Some(components) = remove_components {
224            for component in components {
225                // TODO: handle components which have not been removed
226                let _ = relation_instance_manager.remove_component(&id, &component.into());
227            }
228        }
229        if let Some(properties) = properties {
230            // fill all values first without propagation
231            for property in properties.clone() {
232                debug!("set property {} = {}", property.name.clone(), property.value.clone());
233                // Set with respect to the mutability state
234                relation_instance.set_no_propagate_checked(property.name.clone(), property.value.clone());
235            }
236            // tick every property that has been changed before, this is still not transactional
237            for property in properties {
238                debug!("tick property {} = {}", property.name.clone(), property.value.clone());
239                if let Some(property_instance) = relation_instance.properties.get(property.name.as_str()) {
240                    // Tick with respect to the mutability state
241                    property_instance.tick_checked();
242                }
243            }
244        }
245        if let Some(add_properties) = add_properties {
246            for property_type in add_properties.clone() {
247                let property_type: PropertyType = property_type.into();
248                debug!("add property {} ({})", &property_type.name, &property_type.data_type);
249                relation_instance.add_property_by_type(&property_type);
250            }
251        }
252        if let Some(remove_properties) = remove_properties {
253            for property_name in remove_properties.clone() {
254                debug!("remove property {}", &property_name);
255                relation_instance.remove_property(property_name);
256            }
257        }
258        // TODO: it's still not a transactional mutation
259        // relation_instance.tick();
260        Ok(relation_instance.into())
261    }
262
263    /// Manually tick the relation instance. This means for each property of the entity instance
264    /// the corresponding reactive stream will be activated with it's last value.
265    ///
266    /// This leads to a recalculation if the relation instance is controlled by an behaviour which
267    /// consumes the reactive streams.
268    ///
269    /// In case of the default_connector it does NOT lead to a new value propagation, because the
270    /// reactive streams are not consumed by the default_connector behaviour.
271    async fn tick(&self, context: &Context<'_>, relation_instance_id: GraphQLRelationInstanceId) -> Result<GraphQLRelationInstance> {
272        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
273        let id = relation_instance_id.into();
274        let relation_instance = relation_instance_manager
275            .get(&id)
276            .ok_or_else(|| Error::new(format!("Relation instance {id} does not exist!")))?;
277        relation_instance.tick();
278        Ok(relation_instance.into())
279    }
280
281    /// Deletes a relation instance.
282    async fn delete(&self, context: &Context<'_>, relation_instance_id: GraphQLRelationInstanceId) -> Result<bool> {
283        // let relation_type_manager = context.data::<Arc<dyn RelationTypeManager + Send + Sync>>()?;
284        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
285        let entity_instance_manager = context.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
286
287        debug!("Deleting relation instance {relation_instance_id:?}",);
288
289        if !entity_instance_manager.has(relation_instance_id.outbound_id) {
290            return Err(Error::new(format!("Outbound entity {} does not exist!", relation_instance_id.outbound_id)));
291        }
292
293        if !entity_instance_manager.has(relation_instance_id.inbound_id) {
294            return Err(Error::new(format!("Inbound entity {} does not exist!", relation_instance_id.inbound_id)));
295        }
296
297        Ok(relation_instance_manager.delete(&relation_instance_id.into()))
298    }
299
300    async fn connect(
301        &self,
302        context: &Context<'_>,
303        relation_instance_id: GraphQLRelationInstanceId,
304        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
305    ) -> Result<GraphQLRelationInstance> {
306        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
307        let relation_behaviour_manager = context.data::<Arc<dyn RelationBehaviourManager + Send + Sync>>()?;
308        let relation_component_behaviour_manager = context.data::<Arc<dyn RelationComponentBehaviourManager + Send + Sync>>()?;
309        let relation_instance_id = RelationInstanceId::from(relation_instance_id);
310        let reactive_instance = relation_instance_manager
311            .get(&relation_instance_id)
312            .ok_or(Error::new("Relation instance not found"))?;
313        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
314        if relation_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
315            relation_behaviour_manager
316                .connect(reactive_instance.clone(), &behaviour_ty)
317                .map_err(|e| Error::new(format!("Failed to connect relation behaviour {e:?}")))?;
318        }
319        if relation_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
320            relation_component_behaviour_manager
321                .connect(reactive_instance.clone(), &behaviour_ty)
322                .map_err(|e| Error::new(format!("Failed to connect relation component behaviour {e:?}")))?;
323        }
324        Ok(reactive_instance.into())
325    }
326
327    async fn disconnect(
328        &self,
329        context: &Context<'_>,
330        relation_instance_id: GraphQLRelationInstanceId,
331        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
332    ) -> Result<GraphQLRelationInstance> {
333        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
334        let relation_behaviour_manager = context.data::<Arc<dyn RelationBehaviourManager + Send + Sync>>()?;
335        let relation_component_behaviour_manager = context.data::<Arc<dyn RelationComponentBehaviourManager + Send + Sync>>()?;
336        let relation_instance_id = RelationInstanceId::from(relation_instance_id);
337        let reactive_instance = relation_instance_manager
338            .get(&relation_instance_id)
339            .ok_or(Error::new("Relation instance not found"))?;
340        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
341        if relation_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
342            relation_behaviour_manager
343                .disconnect(reactive_instance.clone(), &behaviour_ty)
344                .map_err(|e| Error::new(format!("Failed to disconnect relation behaviour {e:?}")))?;
345        }
346        if relation_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
347            relation_component_behaviour_manager
348                .disconnect(reactive_instance.clone(), &behaviour_ty)
349                .map_err(|e| Error::new(format!("Failed to disconnect relation component behaviour {e:?}")))?;
350        }
351        Ok(reactive_instance.into())
352    }
353
354    async fn reconnect(
355        &self,
356        context: &Context<'_>,
357        relation_instance_id: GraphQLRelationInstanceId,
358        #[graphql(name = "type")] behaviour_ty: BehaviourTypeIdDefinition,
359    ) -> Result<GraphQLRelationInstance> {
360        let relation_instance_manager = context.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
361        let relation_behaviour_manager = context.data::<Arc<dyn RelationBehaviourManager + Send + Sync>>()?;
362        let relation_component_behaviour_manager = context.data::<Arc<dyn RelationComponentBehaviourManager + Send + Sync>>()?;
363        let relation_instance_id = RelationInstanceId::from(relation_instance_id);
364        let reactive_instance = relation_instance_manager
365            .get(&relation_instance_id)
366            .ok_or(Error::new("Relation instance not found"))?;
367        let behaviour_ty = BehaviourTypeId::from(behaviour_ty);
368        if relation_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
369            relation_behaviour_manager
370                .reconnect(reactive_instance.clone(), &behaviour_ty)
371                .map_err(|e| Error::new(format!("Failed to reconnect relation behaviour {e:?}")))?;
372        }
373        if relation_component_behaviour_manager.has(reactive_instance.clone(), &behaviour_ty) {
374            relation_component_behaviour_manager
375                .reconnect(reactive_instance.clone(), &behaviour_ty)
376                .map_err(|e| Error::new(format!("Failed to reconnect relation component behaviour {e:?}")))?;
377        }
378        Ok(reactive_instance.into())
379    }
380}