async_graphql/dynamic/
object.rs

1use indexmap::{IndexMap, IndexSet};
2
3use super::{Directive, directive::to_meta_directive_invocation};
4use crate::{
5    dynamic::{Field, SchemaError},
6    registry::{MetaField, MetaType, Registry},
7};
8
9/// A GraphQL object type
10///
11/// # Examples
12///
13/// ```
14/// use async_graphql::{dynamic::*, value, Value};
15///
16/// let query = Object::new("Query").field(Field::new("value", TypeRef::named_nn(TypeRef::STRING), |ctx| {
17///     FieldFuture::new(async move { Ok(Some(Value::from("abc"))) })
18/// }));
19///
20/// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
21///
22/// let schema = Schema::build(query.type_name(), None, None)
23///     .register(query)
24///     .finish()?;
25///
26/// assert_eq!(
27///    schema
28///        .execute("{ value }")
29///        .await
30///        .into_result()
31///        .unwrap()
32///        .data,
33///    value!({ "value": "abc" })
34/// );
35///
36/// # Ok::<_, SchemaError>(())
37/// # }).unwrap();
38/// ```
39#[derive(Debug)]
40pub struct Object {
41    pub(crate) name: String,
42    pub(crate) description: Option<String>,
43    pub(crate) fields: IndexMap<String, Field>,
44    pub(crate) implements: IndexSet<String>,
45    keys: Vec<String>,
46    extends: bool,
47    shareable: bool,
48    resolvable: bool,
49    inaccessible: bool,
50    interface_object: bool,
51    tags: Vec<String>,
52    pub(crate) directives: Vec<Directive>,
53    requires_scopes: Vec<String>,
54}
55
56impl Object {
57    /// Create a GraphQL object type
58    #[inline]
59    pub fn new(name: impl Into<String>) -> Self {
60        Self {
61            name: name.into(),
62            description: None,
63            fields: Default::default(),
64            implements: Default::default(),
65            keys: Vec::new(),
66            extends: false,
67            shareable: false,
68            resolvable: true,
69            inaccessible: false,
70            interface_object: false,
71            tags: Vec::new(),
72            directives: Vec::new(),
73            requires_scopes: Vec::new(),
74        }
75    }
76
77    impl_set_description!();
78    impl_set_extends!();
79    impl_set_shareable!();
80    impl_set_inaccessible!();
81    impl_set_interface_object!();
82    impl_set_tags!();
83    impl_directive!();
84
85    /// Add an field to the object
86    #[inline]
87    pub fn field(mut self, field: Field) -> Self {
88        assert!(
89            !self.fields.contains_key(&field.name),
90            "Field `{}` already exists",
91            field.name
92        );
93        self.fields.insert(field.name.clone(), field);
94        self
95    }
96
97    /// Add an implement to the object
98    #[inline]
99    pub fn implement(mut self, interface: impl Into<String>) -> Self {
100        let interface = interface.into();
101        assert!(
102            !self.implements.contains(&interface),
103            "Implement `{}` already exists",
104            interface
105        );
106        self.implements.insert(interface);
107        self
108    }
109
110    /// Add an entity key
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use async_graphql::{Value, dynamic::*};
116    ///
117    /// let obj = Object::new("MyObj")
118    ///     .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
119    ///         FieldFuture::new(async move { Ok(Some(Value::from(10))) })
120    ///     }))
121    ///     .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| {
122    ///         FieldFuture::new(async move { Ok(Some(Value::from(20))) })
123    ///     }))
124    ///     .field(Field::new("c", TypeRef::named(TypeRef::INT), |_| {
125    ///         FieldFuture::new(async move { Ok(Some(Value::from(30))) })
126    ///     }))
127    ///     .key("a b")
128    ///     .key("c");
129    /// ```
130    pub fn key(mut self, fields: impl Into<String>) -> Self {
131        self.keys.push(fields.into());
132        self
133    }
134
135    /// Make the entity unresolvable by the current subgraph
136    ///
137    /// Most commonly used to reference an entity without contributing fields.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use async_graphql::{Value, dynamic::*};
143    ///
144    /// let obj = Object::new("MyObj")
145    ///     .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
146    ///         FieldFuture::new(async move { Ok(Some(Value::from(10))) })
147    ///     }))
148    ///     .unresolvable("a");
149    /// ```
150    ///
151    /// This references the `MyObj` entity with the key `a` that cannot be
152    /// resolved by the current subgraph.
153    pub fn unresolvable(mut self, fields: impl Into<String>) -> Self {
154        self.resolvable = false;
155        self.keys.push(fields.into());
156        self
157    }
158
159    /// Returns the type name
160    #[inline]
161    pub fn type_name(&self) -> &str {
162        &self.name
163    }
164
165    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
166        let mut fields = IndexMap::new();
167
168        for field in self.fields.values() {
169            let mut args = IndexMap::new();
170
171            for argument in field.arguments.values() {
172                args.insert(argument.name.clone(), argument.to_meta_input_value());
173            }
174
175            fields.insert(
176                field.name.clone(),
177                MetaField {
178                    name: field.name.clone(),
179                    description: field.description.clone(),
180                    args,
181                    ty: field.ty.to_string(),
182                    deprecation: field.deprecation.clone(),
183                    cache_control: Default::default(),
184                    external: field.external,
185                    requires: field.requires.clone(),
186                    provides: field.provides.clone(),
187                    visible: None,
188                    shareable: field.shareable,
189                    inaccessible: field.inaccessible,
190                    tags: field.tags.clone(),
191                    override_from: field.override_from.clone(),
192                    compute_complexity: None,
193                    directive_invocations: to_meta_directive_invocation(field.directives.clone()),
194                    requires_scopes: field.requires_scopes.clone(),
195                },
196            );
197        }
198
199        registry.types.insert(
200            self.name.clone(),
201            MetaType::Object {
202                name: self.name.clone(),
203                description: self.description.clone(),
204                fields,
205                cache_control: Default::default(),
206                extends: self.extends,
207                shareable: self.shareable,
208                resolvable: self.resolvable,
209                keys: if !self.keys.is_empty() {
210                    Some(self.keys.clone())
211                } else {
212                    None
213                },
214                visible: None,
215                inaccessible: self.inaccessible,
216                interface_object: self.interface_object,
217                tags: self.tags.clone(),
218                is_subscription: false,
219                rust_typename: None,
220                directive_invocations: to_meta_directive_invocation(self.directives.clone()),
221                requires_scopes: self.requires_scopes.clone(),
222            },
223        );
224
225        for interface in &self.implements {
226            registry.add_implements(&self.name, interface);
227        }
228
229        Ok(())
230    }
231
232    #[inline]
233    pub(crate) fn is_entity(&self) -> bool {
234        !self.keys.is_empty()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use crate::{Value, dynamic::*, value};
241
242    #[tokio::test]
243    async fn borrow_context() {
244        struct MyObjData {
245            value: i32,
246        }
247
248        let my_obj =
249            Object::new("MyObj").field(Field::new("value", TypeRef::named(TypeRef::INT), |ctx| {
250                FieldFuture::new(async move {
251                    Ok(Some(Value::from(
252                        ctx.parent_value.try_downcast_ref::<MyObjData>()?.value,
253                    )))
254                })
255            }));
256
257        let query = Object::new("Query").field(Field::new(
258            "obj",
259            TypeRef::named_nn(my_obj.type_name()),
260            |ctx| {
261                FieldFuture::new(async move {
262                    Ok(Some(FieldValue::borrowed_any(
263                        ctx.data_unchecked::<MyObjData>(),
264                    )))
265                })
266            },
267        ));
268
269        let schema = Schema::build("Query", None, None)
270            .register(query)
271            .register(my_obj)
272            .data(MyObjData { value: 123 })
273            .finish()
274            .unwrap();
275
276        assert_eq!(
277            schema
278                .execute("{ obj { value } }")
279                .await
280                .into_result()
281                .unwrap()
282                .data,
283            value!({
284                "obj": {
285                    "value": 123,
286                }
287            })
288        );
289    }
290}