async_graphql/dynamic/
object.rs

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