Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/async-graphql-7.0.15/src/dynamic/object.rs
Line
Count
Source (jump to first uncovered line)
1
use indexmap::{IndexMap, IndexSet};
2
3
use super::{directive::to_meta_directive_invocation, Directive};
4
use 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)]
40
pub 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
55
impl Object {
56
    /// Create a GraphQL object type
57
    #[inline]
58
0
    pub fn new(name: impl Into<String>) -> Self {
59
0
        Self {
60
0
            name: name.into(),
61
0
            description: None,
62
0
            fields: Default::default(),
63
0
            implements: Default::default(),
64
0
            keys: Vec::new(),
65
0
            extends: false,
66
0
            shareable: false,
67
0
            resolvable: true,
68
0
            inaccessible: false,
69
0
            interface_object: false,
70
0
            tags: Vec::new(),
71
0
            directives: Vec::new(),
72
0
        }
73
0
    }
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
0
    pub fn field(mut self, field: Field) -> Self {
86
0
        assert!(
87
0
            !self.fields.contains_key(&field.name),
88
0
            "Field `{}` already exists",
89
            field.name
90
        );
91
0
        self.fields.insert(field.name.clone(), field);
92
0
        self
93
0
    }
94
95
    /// Add an implement to the object
96
    #[inline]
97
0
    pub fn implement(mut self, interface: impl Into<String>) -> Self {
98
0
        let interface = interface.into();
99
0
        assert!(
100
0
            !self.implements.contains(&interface),
101
0
            "Implement `{}` already exists",
102
            interface
103
        );
104
0
        self.implements.insert(interface);
105
0
        self
106
0
    }
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
0
    pub fn key(mut self, fields: impl Into<String>) -> Self {
129
0
        self.keys.push(fields.into());
130
0
        self
131
0
    }
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
0
    pub fn unresolvable(mut self, fields: impl Into<String>) -> Self {
152
0
        self.resolvable = false;
153
0
        self.keys.push(fields.into());
154
0
        self
155
0
    }
156
157
    /// Returns the type name
158
    #[inline]
159
0
    pub fn type_name(&self) -> &str {
160
0
        &self.name
161
0
    }
162
163
0
    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
164
0
        let mut fields = IndexMap::new();
165
166
0
        for field in self.fields.values() {
167
0
            let mut args = IndexMap::new();
168
169
0
            for argument in field.arguments.values() {
170
0
                args.insert(argument.name.clone(), argument.to_meta_input_value());
171
0
            }
172
173
0
            fields.insert(
174
0
                field.name.clone(),
175
0
                MetaField {
176
0
                    name: field.name.clone(),
177
0
                    description: field.description.clone(),
178
0
                    args,
179
0
                    ty: field.ty.to_string(),
180
0
                    deprecation: field.deprecation.clone(),
181
0
                    cache_control: Default::default(),
182
0
                    external: field.external,
183
0
                    requires: field.requires.clone(),
184
0
                    provides: field.provides.clone(),
185
0
                    visible: None,
186
0
                    shareable: field.shareable,
187
0
                    inaccessible: field.inaccessible,
188
0
                    tags: field.tags.clone(),
189
0
                    override_from: field.override_from.clone(),
190
0
                    compute_complexity: None,
191
0
                    directive_invocations: to_meta_directive_invocation(field.directives.clone()),
192
0
                },
193
0
            );
194
        }
195
196
0
        registry.types.insert(
197
0
            self.name.clone(),
198
0
            MetaType::Object {
199
0
                name: self.name.clone(),
200
0
                description: self.description.clone(),
201
0
                fields,
202
0
                cache_control: Default::default(),
203
0
                extends: self.extends,
204
0
                shareable: self.shareable,
205
0
                resolvable: self.resolvable,
206
0
                keys: if !self.keys.is_empty() {
207
0
                    Some(self.keys.clone())
208
                } else {
209
0
                    None
210
                },
211
0
                visible: None,
212
0
                inaccessible: self.inaccessible,
213
0
                interface_object: self.interface_object,
214
0
                tags: self.tags.clone(),
215
0
                is_subscription: false,
216
0
                rust_typename: None,
217
0
                directive_invocations: to_meta_directive_invocation(self.directives.clone()),
218
            },
219
        );
220
221
0
        for interface in &self.implements {
222
0
            registry.add_implements(&self.name, interface);
223
0
        }
224
225
0
        Ok(())
226
0
    }
227
228
    #[inline]
229
0
    pub(crate) fn is_entity(&self) -> bool {
230
0
        !self.keys.is_empty()
231
0
    }
232
}
233
234
#[cfg(test)]
235
mod 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
}