reactive_graph_dynamic_graph_impl/field/
relation.rs

1use std::str::FromStr;
2use std::sync::Arc;
3
4use async_graphql::Error;
5use async_graphql::ID;
6use async_graphql::dynamic::*;
7use reactive_graph_reactive_service_api::ReactiveRelationRegistrationError;
8use serde_json::Value;
9use uuid::Uuid;
10
11use crate::field::create_properties_from_field_arguments;
12use crate::field::to_field_value;
13use crate::field::to_input_type_ref;
14use crate::field::to_type_ref;
15use crate::interface::relation::INTERFACE_RELATION_FIELD_INSTANCE_ID;
16use crate::interface::relation::INTERFACE_RELATION_FIELD_KEY;
17use crate::object::types::DynamicGraphTypeDefinition;
18use crate::union::entity::UNION_ALL_ENTITIES;
19use crate::union::entity::namespace_entities_union_type_name;
20use reactive_graph_dynamic_graph_api::SchemaBuilderContext;
21use reactive_graph_graph::ComponentOrEntityTypeId;
22use reactive_graph_graph::ComponentTypeId;
23use reactive_graph_graph::DataType;
24use reactive_graph_graph::EntityTypeId;
25use reactive_graph_graph::NamespacedTypeGetter;
26use reactive_graph_graph::PropertyInstanceGetter;
27use reactive_graph_graph::PropertyType;
28use reactive_graph_graph::PropertyTypeDefinition;
29use reactive_graph_graph::RelationInstanceId;
30use reactive_graph_graph::RelationInstanceTypeId;
31use reactive_graph_graph::RelationType;
32use reactive_graph_reactive_model_impl::ReactiveProperties;
33use reactive_graph_reactive_model_impl::ReactiveRelation;
34use reactive_graph_reactive_service_api::ReactiveEntityManager;
35use reactive_graph_reactive_service_api::ReactiveRelationCreationError;
36use reactive_graph_reactive_service_api::ReactiveRelationManager;
37use reactive_graph_runtime_model::LabeledProperties::LABEL;
38
39pub fn relation_query_field(relation_type: &RelationType) -> Field {
40    let ty = relation_type.ty.clone();
41    let relation_type_inner = relation_type.clone();
42    let dy_ty = DynamicGraphTypeDefinition::from(&ty);
43    let mut field = Field::new(dy_ty.field_name(), TypeRef::named_nn_list_nn(dy_ty.to_string()), move |ctx| {
44        let ty = ty.clone();
45        let relation_type = relation_type_inner.clone();
46        FieldFuture::new(async move {
47            let relation_instance_manager = ctx.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
48            let mut instances = relation_instance_manager.get_by_type(&ty);
49            for property in relation_type.properties.iter() {
50                let Some(expected_value) = ctx.args.get(&property.name) else {
51                    continue;
52                };
53                instances.retain(|instance| match instance.get(&property.name) {
54                    Some(actual_value) => match &property.data_type {
55                        DataType::Null => false,
56                        DataType::Bool => expected_value
57                            .boolean()
58                            .map(|expected_value| actual_value.as_bool().map(|actual_value| expected_value == actual_value).unwrap_or(false))
59                            .unwrap_or(false),
60                        DataType::Number => {
61                            if let Ok(expected_value) = expected_value.i64() {
62                                actual_value.as_i64().map(|actual_value| expected_value == actual_value).unwrap_or(false)
63                            } else if let Ok(expected_value) = expected_value.u64() {
64                                actual_value.as_u64().map(|actual_value| expected_value == actual_value).unwrap_or(false)
65                            } else if let Ok(expected_value) = expected_value.f64() {
66                                actual_value.as_f64().map(|actual_value| expected_value == actual_value).unwrap_or(false)
67                            } else {
68                                false
69                            }
70                        }
71                        DataType::String => expected_value
72                            .string()
73                            .map(|expected_value| actual_value.as_str().map(|actual_value| expected_value == actual_value).unwrap_or(false))
74                            .unwrap_or(false),
75                        DataType::Array => {
76                            if let Ok(_l) = expected_value.list() {
77                                if let Ok(expected_value) = expected_value.deserialize::<Value>() {
78                                    if expected_value.is_array() && actual_value.is_array() {
79                                        expected_value == actual_value
80                                    } else {
81                                        false
82                                    }
83                                } else {
84                                    false
85                                }
86                            } else {
87                                false
88                            }
89                        }
90                        DataType::Object => {
91                            if let Ok(_o) = expected_value.object() {
92                                if let Ok(expected_value) = expected_value.deserialize::<Value>() {
93                                    if expected_value.is_object() && actual_value.is_object() {
94                                        expected_value == actual_value
95                                    } else {
96                                        false
97                                    }
98                                } else {
99                                    false
100                                }
101                            } else {
102                                false
103                            }
104                        }
105                        DataType::Any => match expected_value.deserialize::<Value>() {
106                            Ok(expected_value) => expected_value == actual_value,
107                            Err(_) => false,
108                        },
109                    },
110                    None => false,
111                });
112            }
113            Ok(Some(FieldValue::list(instances.into_iter().map(FieldValue::owned_any))))
114        })
115    })
116    .description(relation_type.description.clone());
117    for property in relation_type.properties.iter() {
118        if property.name == LABEL.property_name() {
119            continue;
120        }
121        if let Some(type_ref) = to_input_type_ref(property.value(), true) {
122            field = field.argument(InputValue::new(&property.name, type_ref));
123        }
124    }
125    field
126}
127
128pub fn relation_creation_field(relation_type: &RelationType) -> Option<Field> {
129    let ty = relation_type.ty.clone();
130    let relation_type_inner = relation_type.clone();
131    let dy_ty = DynamicGraphTypeDefinition::from(&ty);
132    let mut field = Field::new(dy_ty.mutation_field_name("create"), TypeRef::named_nn(dy_ty.to_string()), move |ctx| {
133        let ty = ty.clone();
134        let relation_type = relation_type_inner.clone();
135        FieldFuture::new(async move {
136            let entity_instance_manager = ctx.data::<Arc<dyn ReactiveEntityManager + Send + Sync>>()?;
137            let relation_instance_manager = ctx.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
138
139            let outbound_id = Uuid::from_str(ctx.args.try_get("outboundId")?.string()?)?;
140            let inbound_id = Uuid::from_str(ctx.args.try_get("inboundId")?.string()?)?;
141            let rty = match ctx.args.get("instanceId").and_then(|s| s.string().map(|s| s.to_string()).ok()) {
142                Some(instance_id) => RelationInstanceTypeId::new_unique_for_instance_id(ty, instance_id),
143                None => RelationInstanceTypeId::new_with_random_instance_id(ty),
144            };
145            let id = RelationInstanceId::builder().outbound_id(outbound_id).ty(&rty).inbound_id(inbound_id).build();
146
147            if relation_instance_manager.has(&id) {
148                return Err(ReactiveRelationRegistrationError::RelationInstanceAlreadyExists(id.clone()).into());
149            }
150
151            let outbound = entity_instance_manager
152                .get(outbound_id)
153                .ok_or::<Error>(ReactiveRelationCreationError::MissingOutboundEntityInstance(outbound_id).into())?;
154
155            let inbound = entity_instance_manager
156                .get(inbound_id)
157                .ok_or::<Error>(ReactiveRelationCreationError::MissingInboundEntityInstance(inbound_id).into())?;
158
159            let properties = create_properties_from_field_arguments(&ctx, &relation_type.properties)?;
160            let properties = ReactiveProperties::new_with_id_from_properties(id, properties);
161            let reactive_relation = ReactiveRelation::builder()
162                .outbound(outbound)
163                .ty(&rty)
164                .inbound(inbound)
165                .properties(properties)
166                .build();
167            if let Ok(reactive_relation) = relation_instance_manager.register_reactive_instance(reactive_relation) {
168                return Ok(Some(FieldValue::owned_any(reactive_relation)));
169            }
170            Ok(None)
171        })
172    })
173    .argument(InputValue::new("outboundId", TypeRef::named(TypeRef::ID)))
174    .argument(InputValue::new("instanceId", TypeRef::named(TypeRef::ID)))
175    .argument(InputValue::new("inboundId", TypeRef::named(TypeRef::ID)));
176    for property in relation_type.properties.iter() {
177        if let Some(type_ref) = to_input_type_ref(property.value(), false) {
178            field = field.argument(InputValue::new(&property.name, type_ref));
179        }
180    }
181    Some(field)
182}
183
184pub fn relation_mutation_field(relation_type: &RelationType) -> Option<Field> {
185    let ty = relation_type.ty.clone();
186    let dy_ty = DynamicGraphTypeDefinition::from(&ty);
187    let field = Field::new(dy_ty.field_name(), TypeRef::named_nn(dy_ty.mutation_type_name()), move |ctx| {
188        let ty = ty.clone();
189        FieldFuture::new(async move {
190            let relation_instance_manager = ctx.data::<Arc<dyn ReactiveRelationManager + Send + Sync>>()?;
191            let relation_instances: Vec<ReactiveRelation> = relation_instance_manager
192                .get_by_type(&ty)
193                .into_iter()
194                .filter(|relation_instance| {
195                    let Ok(id) = ctx.args.try_get("outboundId").and_then(|id| id.string().map(|s| s.to_string())) else {
196                        return true;
197                    };
198                    let Ok(id) = Uuid::from_str(&id) else {
199                        return true;
200                    };
201                    relation_instance.outbound.id == id
202                })
203                .filter(|relation_instance| {
204                    let Ok(id) = ctx.args.try_get("inboundId").and_then(|id| id.string().map(|s| s.to_string())) else {
205                        return true;
206                    };
207                    let Ok(id) = Uuid::from_str(&id) else {
208                        return true;
209                    };
210                    relation_instance.outbound.id == id
211                })
212                // TODO: implement outbound_type search
213                // TODO: implement inbound_type search
214                // TODO: implement label matching
215                // TODO: implement property search
216                .collect();
217            let field_value = FieldValue::owned_any(relation_instances);
218            Ok(Some(field_value))
219        })
220    })
221    // .argument(InputValue::new("ids", TypeRef::named_nn_list(TypeRef::ID)))
222    .argument(InputValue::new("outboundId", TypeRef::named(TypeRef::ID)))
223    .argument(InputValue::new("inboundId", TypeRef::named(TypeRef::ID)))
224    // TODO: implement label matching
225    .argument(InputValue::new("label", TypeRef::named(TypeRef::STRING)))
226    // TODO: implement property search
227    // for property in properties {
228    //   .argument(InputValue::new(property.name, TypeRef::named_nn_list(type_ref_properties(property))))
229    .description(relation_type.description.clone());
230    Some(field)
231}
232
233pub fn relation_key_field() -> Field {
234    Field::new(INTERFACE_RELATION_FIELD_KEY, TypeRef::named_nn(TypeRef::ID), |ctx| {
235        FieldFuture::new(async move {
236            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
237            Ok(Some(FieldValue::value(ID(format!("{}", relation_instance.ty)))))
238        })
239    })
240}
241
242pub fn relation_instance_id_field() -> Field {
243    Field::new(INTERFACE_RELATION_FIELD_INSTANCE_ID, TypeRef::named_nn(TypeRef::ID), |ctx| {
244        FieldFuture::new(async move {
245            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
246            Ok(Some(FieldValue::value(ID(relation_instance.instance_id()))))
247        })
248    })
249}
250
251pub fn relation_property_field(property_type: &PropertyType) -> Field {
252    let property_type_inner = property_type.clone();
253    Field::new(&property_type.name, to_type_ref(&property_type.data_type), move |ctx| {
254        let property_type = property_type_inner.clone();
255        FieldFuture::new(async move {
256            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
257            Ok(relation_instance.get(&property_type.name).and_then(to_field_value))
258        })
259    })
260    .description(&property_type.description)
261}
262
263pub fn relation_outbound_field(
264    ty: &ComponentOrEntityTypeId,
265    field_name: Option<String>,
266    field_description: Option<String>,
267    context: &SchemaBuilderContext,
268) -> Vec<Field> {
269    match ty {
270        ComponentOrEntityTypeId::EntityType(ty) => {
271            if ty.namespace() == "*" {
272                vec![relation_outbound_entity_union_field(UNION_ALL_ENTITIES, field_name, field_description)]
273            } else if ty.type_name() == "*" {
274                vec![relation_outbound_entity_union_field(
275                    &namespace_entities_union_type_name(&ty.namespace()),
276                    field_name,
277                    field_description,
278                )]
279            } else {
280                vec![relation_outbound_entity_field(ty, field_name, field_description)]
281            }
282        }
283        ComponentOrEntityTypeId::Component(ty) => {
284            if ty.namespace() == "*" {
285                context
286                    .component_manager
287                    .get_type_ids()
288                    .iter()
289                    .map(|ty| relation_outbound_component_field(ty.key(), None, None))
290                    .collect()
291                // .get_all()
292                // .into_iter()
293                // // .map(|component| component.ty)
294                // .map(|(component_ty, component)| relation_outbound_component_field(&component_ty, None, None))
295                // .collect()
296            } else if ty.type_name() == "*" {
297                context
298                    .component_manager
299                    .get_types_by_namespace(&ty.namespace())
300                    .iter()
301                    .map(|ty| relation_outbound_component_field(ty.key(), None, None))
302                    .collect()
303                // .get_by_namespace(&ty.namespace())
304                // .into_iter()
305                // // .map(|component| component.ty)
306                // .map(|(component_ty, component)| relation_outbound_component_field(&component_ty, None, None))
307                // .collect()
308            } else {
309                vec![relation_outbound_component_field(ty, field_name, field_description)]
310            }
311        }
312    }
313}
314
315pub fn relation_inbound_field(
316    ty: &ComponentOrEntityTypeId,
317    field_name: Option<String>,
318    field_description: Option<String>,
319    context: &SchemaBuilderContext,
320) -> Vec<Field> {
321    match ty {
322        ComponentOrEntityTypeId::EntityType(ty) => {
323            if ty.namespace() == "*" {
324                vec![relation_inbound_entity_union_field(UNION_ALL_ENTITIES, field_name, field_description)]
325            } else if ty.type_name() == "*" {
326                vec![relation_inbound_entity_union_field(
327                    &namespace_entities_union_type_name(&ty.namespace()),
328                    field_name,
329                    field_description,
330                )]
331            } else {
332                vec![relation_inbound_entity_field(ty, field_name, field_description)]
333            }
334        }
335        ComponentOrEntityTypeId::Component(ty) => {
336            if ty.namespace() == "*" {
337                context
338                    .component_manager
339                    .get_type_ids()
340                    .iter()
341                    .map(|ty| relation_inbound_component_field(ty.key(), None, None))
342                    .collect()
343                // .get_all()
344                // .into_iter()
345                // .map(|(component_ty, component)| relation_inbound_component_field(&component_ty, None, None))
346                // .collect()
347            } else if ty.type_name() == "*" {
348                context
349                    .component_manager
350                    .get_types_by_namespace(&ty.namespace())
351                    .iter()
352                    .map(|ty| relation_inbound_component_field(ty.key(), None, None))
353                    .collect()
354                // .get_by_namespace(&ty.namespace())
355                // .into_iter()
356                // .map(|(component_ty, component)| relation_inbound_component_field(&component_ty, None, None))
357                // .collect()
358            } else {
359                vec![relation_inbound_component_field(ty, field_name, field_description)]
360            }
361        }
362    }
363}
364
365pub fn relation_outbound_entity_field(ty: &EntityTypeId, field_name: Option<String>, field_description: Option<String>) -> Field {
366    let dy_ty = DynamicGraphTypeDefinition::from(ty);
367    let field_name = field_name.unwrap_or(dy_ty.outbound_type_name());
368    create_relation_outbound_field(&dy_ty.to_string(), &field_name, field_description)
369}
370
371pub fn relation_outbound_entity_union_field(type_name: &str, field_name: Option<String>, field_description: Option<String>) -> Field {
372    let field_name = field_name.unwrap_or("outbound".to_string());
373    let mut field = Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
374        FieldFuture::new(async move {
375            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
376            let dy_ty = DynamicGraphTypeDefinition::from(&relation_instance.outbound.ty);
377            Ok(Some(FieldValue::owned_any(relation_instance.outbound.clone()).with_type(dy_ty.to_string())))
378        })
379    });
380    if let Some(field_description) = field_description {
381        field = field.description(field_description);
382    }
383    field
384}
385
386pub fn relation_outbound_component_field(ty: &ComponentTypeId, field_name: Option<String>, field_description: Option<String>) -> Field {
387    let dy_ty = DynamicGraphTypeDefinition::from(ty);
388    let field_name = field_name.unwrap_or(dy_ty.outbound_type_name());
389    create_relation_outbound_field(&dy_ty.to_string(), &field_name, field_description)
390}
391
392pub fn create_relation_outbound_field(type_name: &str, field_name: &str, field_description: Option<String>) -> Field {
393    let mut field = Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
394        FieldFuture::new(async move {
395            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
396            Ok(Some(FieldValue::owned_any(relation_instance.outbound.clone())))
397        })
398    });
399    if let Some(field_description) = field_description {
400        field = field.description(field_description);
401    }
402    field
403}
404
405pub fn relation_inbound_entity_field(ty: &EntityTypeId, field_name: Option<String>, field_description: Option<String>) -> Field {
406    let dy_ty = DynamicGraphTypeDefinition::from(ty);
407    let field_name = field_name.unwrap_or(dy_ty.inbound_type_name());
408    create_relation_inbound_field(&dy_ty.to_string(), &field_name, field_description)
409}
410
411pub fn relation_inbound_entity_union_field(type_name: &str, field_name: Option<String>, field_description: Option<String>) -> Field {
412    let field_name = field_name.unwrap_or("inbound".to_string());
413    let mut field = Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
414        FieldFuture::new(async move {
415            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
416            let dy_ty = DynamicGraphTypeDefinition::from(&relation_instance.inbound.ty);
417            Ok(Some(FieldValue::owned_any(relation_instance.inbound.clone()).with_type(dy_ty.to_string())))
418        })
419    });
420    if let Some(field_description) = field_description {
421        field = field.description(field_description);
422    }
423    field
424}
425
426pub fn relation_inbound_component_field(ty: &ComponentTypeId, field_name: Option<String>, field_description: Option<String>) -> Field {
427    let dy_ty = DynamicGraphTypeDefinition::from(ty);
428    let field_name = field_name.unwrap_or(dy_ty.inbound_type_name());
429    create_relation_inbound_field(&dy_ty.to_string(), &field_name, field_description)
430}
431
432pub fn create_relation_inbound_field(type_name: &str, field_name: &str, field_description: Option<String>) -> Field {
433    let mut field = Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| {
434        FieldFuture::new(async move {
435            let relation_instance = ctx.parent_value.try_downcast_ref::<ReactiveRelation>()?;
436            Ok(Some(FieldValue::owned_any(relation_instance.inbound.clone())))
437        })
438    });
439    if let Some(field_description) = field_description {
440        field = field.description(field_description);
441    }
442    field
443}