apollo_compiler/introspection/
resolvers.rs

1use crate::collections::HashMap;
2use crate::execution::resolver::ResolvedValue;
3use crate::execution::resolver::Resolver;
4use crate::execution::resolver::ResolverError;
5use crate::response::JsonMap;
6use crate::schema;
7use crate::schema::Implementers;
8use crate::schema::Name;
9use crate::Node;
10use crate::Schema;
11use std::borrow::Cow;
12
13#[derive(Clone, Copy)]
14pub(crate) struct SchemaWithImplementersMap<'a> {
15    pub(crate) schema: &'a Schema,
16    pub(crate) implementers_map: &'a HashMap<Name, Implementers>,
17}
18
19impl<'a> SchemaWithImplementersMap<'a> {
20    fn implementers_of(&self, interface_name: &str) -> impl Iterator<Item = &'a Name> {
21        self.implementers_map
22            .get(interface_name)
23            .into_iter()
24            .flat_map(|implementers| &implementers.objects)
25    }
26}
27
28impl<'a> std::ops::Deref for SchemaWithImplementersMap<'a> {
29    type Target = &'a Schema;
30
31    fn deref(&self) -> &Self::Target {
32        &self.schema
33    }
34}
35
36pub(crate) struct IntrospectionRootResolver<'a>(pub(crate) SchemaWithImplementersMap<'a>);
37
38struct TypeDefResolver<'a> {
39    schema: SchemaWithImplementersMap<'a>,
40    name: &'a str,
41    def: &'a schema::ExtendedType,
42}
43
44/// Only used for non-null and list types. `TypeDef` is used for everything else.
45pub(crate) struct TypeResolver<'a> {
46    schema: SchemaWithImplementersMap<'a>,
47    ty: Cow<'a, schema::Type>,
48}
49
50struct DirectiveResolver<'a> {
51    schema: SchemaWithImplementersMap<'a>,
52    def: &'a schema::DirectiveDefinition,
53}
54
55struct FieldResolver<'a> {
56    schema: SchemaWithImplementersMap<'a>,
57    def: &'a schema::FieldDefinition,
58}
59
60struct EnumValueResolver<'a> {
61    schema: SchemaWithImplementersMap<'a>,
62    def: &'a schema::EnumValueDefinition,
63}
64
65struct InputValueResolver<'a> {
66    schema: SchemaWithImplementersMap<'a>,
67    def: &'a schema::InputValueDefinition,
68}
69
70fn type_def(schema: SchemaWithImplementersMap<'_>, name: impl AsRef<str>) -> ResolvedValue<'_> {
71    ResolvedValue::opt_object(
72        schema
73            .types
74            .get_key_value(name.as_ref())
75            .map(|(name, def)| TypeDefResolver { schema, name, def }),
76    )
77}
78
79fn type_def_opt<'a>(
80    schema: SchemaWithImplementersMap<'a>,
81    name: &Option<impl AsRef<str>>,
82) -> ResolvedValue<'a> {
83    if let Some(name) = name.as_ref() {
84        type_def(schema, name)
85    } else {
86        ResolvedValue::null()
87    }
88}
89
90fn ty<'a>(schema: SchemaWithImplementersMap<'a>, ty: &'a schema::Type) -> ResolvedValue<'a> {
91    if let schema::Type::Named(name) = ty {
92        type_def(schema, name)
93    } else {
94        ResolvedValue::object(TypeResolver {
95            schema,
96            ty: Cow::Borrowed(ty),
97        })
98    }
99}
100
101fn deprecation_reason<'a>(
102    schema: &SchemaWithImplementersMap<'_>,
103    opt_directive: Option<&Node<schema::Directive>>,
104) -> ResolvedValue<'a> {
105    ResolvedValue::leaf(
106        opt_directive
107            .and_then(|directive| directive.argument_by_name("reason", schema.schema).ok())
108            .and_then(|arg| arg.as_str()),
109    )
110}
111
112impl Resolver for IntrospectionRootResolver<'_> {
113    fn type_name(&self) -> &'static str {
114        unreachable!()
115    }
116
117    fn resolve_field<'a>(
118        &'a self,
119        field_name: &'a str,
120        arguments: &'a JsonMap,
121    ) -> Result<ResolvedValue<'a>, ResolverError> {
122        match field_name {
123            "__schema" => Ok(ResolvedValue::object(self.0)),
124            "__type" => {
125                let name = arguments["name"].as_str().unwrap();
126                Ok(type_def(self.0, name))
127            }
128            // "__typename" is handled in `execute_selection_set` without calling here.
129            // Other fields are skipped in `skip_field` below.
130            _ => unreachable!(),
131        }
132    }
133
134    fn skip_field(&self, field_name: &str) -> bool {
135        !matches!(field_name, "__schema" | "__type" | "__typename")
136    }
137}
138
139impl_resolver! {
140    for SchemaWithImplementersMap<'_>:
141
142    __typename = "__Schema";
143
144    fn description(&self_) {
145        Ok(ResolvedValue::leaf(self_.schema_definition.description.as_deref()))
146    }
147
148    fn types(&self_) {
149        Ok(ResolvedValue::list(self_.types.iter().map(|(name, def)| {
150            ResolvedValue::object(TypeDefResolver { schema: *self_, name, def })
151        })))
152    }
153
154    fn directives(&self_) {
155        Ok(ResolvedValue::list(self_.directive_definitions.values().map(|def| {
156            ResolvedValue::object(DirectiveResolver { schema: *self_, def })
157        })))
158    }
159
160    fn queryType(&self_) {
161        Ok(type_def_opt(*self_, &self_.schema_definition.query))
162    }
163
164    fn mutationType(&self_) {
165        Ok(type_def_opt(*self_, &self_.schema_definition.mutation))
166    }
167
168    fn subscriptionType(&self_) {
169        Ok(type_def_opt(*self_, &self_.schema_definition.subscription))
170    }
171}
172
173impl_resolver! {
174    for TypeDefResolver<'_>:
175
176    __typename = "__Type";
177
178    fn kind(&self_) {
179        Ok(ResolvedValue::leaf(match self_.def {
180            schema::ExtendedType::Scalar(_) => "SCALAR",
181            schema::ExtendedType::Object(_) => "OBJECT",
182            schema::ExtendedType::Interface(_) => "INTERFACE",
183            schema::ExtendedType::Union(_) => "UNION",
184            schema::ExtendedType::Enum(_) => "ENUM",
185            schema::ExtendedType::InputObject(_) => "INPUT_OBJECT",
186        }))
187    }
188
189    fn name(&self_) {
190        Ok(ResolvedValue::leaf(self_.name))
191    }
192
193    fn description(&self_) {
194        Ok(ResolvedValue::leaf(self_.def.description().map(|desc| desc.as_str())))
195    }
196
197    fn fields(&self_, args) {
198        let fields = match self_.def {
199            schema::ExtendedType::Object(def) => &def.fields,
200            schema::ExtendedType::Interface(def) => &def.fields,
201            schema::ExtendedType::Scalar(_) |
202            schema::ExtendedType::Union(_) |
203            schema::ExtendedType::Enum(_) |
204            schema::ExtendedType::InputObject(_) => return Ok(ResolvedValue::null()),
205        };
206        let include_deprecated = include_deprecated(args);
207        Ok(ResolvedValue::list(fields
208            .values()
209            .filter(move |def| {
210                include_deprecated || def.directives.get("deprecated").is_none()
211            })
212            .map(|def| {
213                ResolvedValue::object(FieldResolver { schema: self_.schema, def })
214            })
215        ))
216    }
217
218    fn interfaces(&self_) {
219        let implements_interfaces = match self_.def {
220            schema::ExtendedType::Object(def) => &def.implements_interfaces,
221            schema::ExtendedType::Interface(def) => &def.implements_interfaces,
222            schema::ExtendedType::Scalar(_) |
223            schema::ExtendedType::Union(_) |
224            schema::ExtendedType::Enum(_) |
225            schema::ExtendedType::InputObject(_) => return Ok(ResolvedValue::null()),
226        };
227        Ok(ResolvedValue::list(implements_interfaces.iter().filter_map(|name| {
228            self_.schema.types.get(&name.name).map(|def| {
229                ResolvedValue::object(TypeDefResolver { schema: self_.schema, name, def })
230            })
231        })))
232    }
233
234    fn possibleTypes(&self_) {
235        macro_rules! types {
236            ($names: expr) => {
237                Ok(ResolvedValue::list($names.filter_map(move |name| {
238                    self_.schema.types.get(name).map(move |def| {
239                        ResolvedValue::object(TypeDefResolver { schema: self_.schema, name, def })
240                    })
241                })))
242            }
243        }
244        match self_.def {
245            schema::ExtendedType::Interface(_) => types!(self_.schema.implementers_of(self_.name)),
246            schema::ExtendedType::Union(def) => types!(def.members.iter().map(|c| &c.name)),
247            schema::ExtendedType::Object(_) |
248            schema::ExtendedType::Scalar(_) |
249            schema::ExtendedType::Enum(_) |
250            schema::ExtendedType::InputObject(_) => Ok(ResolvedValue::null()),
251        }
252    }
253
254    fn enumValues(&self_, args) {
255        let schema::ExtendedType::Enum(def) = self_.def else {
256            return Ok(ResolvedValue::null());
257        };
258        let include_deprecated = include_deprecated(args);
259        Ok(ResolvedValue::list(def
260            .values
261            .values()
262            .filter(move |def| {
263                include_deprecated || def.directives.get("deprecated").is_none()
264            })
265            .map(|def| {
266                ResolvedValue::object(EnumValueResolver { schema: self_.schema, def })
267            })
268        ))
269    }
270
271    fn inputFields(&self_, args) {
272        let schema::ExtendedType::InputObject(def) = self_.def else {
273            return Ok(ResolvedValue::null());
274        };
275        let include_deprecated = include_deprecated(args);
276        Ok(ResolvedValue::list(def
277            .fields
278            .values()
279            .filter(move |def| {
280                include_deprecated || def.directives.get("deprecated").is_none()
281            })
282            .map(|def| {
283                ResolvedValue::object(InputValueResolver { schema: self_.schema, def })
284            })
285        ))
286    }
287
288    fn ofType() {
289        Ok(ResolvedValue::null())
290    }
291
292    fn specifiedByURL(&self_) {
293        let schema::ExtendedType::Scalar(def) = self_.def else {
294            return Ok(ResolvedValue::null())
295        };
296        Ok(ResolvedValue::leaf(def
297            .directives.get("specifiedBy")
298            .and_then(|dir| dir.specified_argument_by_name("url"))
299            .and_then(|arg| arg.as_str())
300        ))
301    }
302}
303
304// Only used for non-null and list types
305impl_resolver! {
306    for TypeResolver<'_>:
307
308    __typename = "__Type";
309
310    fn kind(&self_) {
311        Ok(ResolvedValue::leaf(match &*self_.ty {
312            schema::Type::Named(_) => unreachable!(),
313            schema::Type::List(_) => "LIST",
314            schema::Type::NonNullNamed(_) |
315            schema::Type::NonNullList(_) => "NON_NULL",
316        }))
317    }
318
319    fn ofType(&self_) {
320        Ok(match &*self_.ty {
321            schema::Type::Named(_) => unreachable!(),
322            schema::Type::List(inner) => ty(self_.schema, inner),
323            schema::Type::NonNullNamed(inner) => type_def(self_.schema, inner),
324            schema::Type::NonNullList(inner) => ResolvedValue::object(Self {
325                schema: self_.schema,
326                ty: Cow::Owned(schema::Type::List(inner.clone()))
327            }),
328        })
329    }
330
331    fn name() { Ok(ResolvedValue::null()) }
332    fn description() { Ok(ResolvedValue::null()) }
333    fn fields() { Ok(ResolvedValue::null()) }
334    fn interfaces() { Ok(ResolvedValue::null()) }
335    fn possibleTypes() { Ok(ResolvedValue::null()) }
336    fn enumValues() { Ok(ResolvedValue::null()) }
337    fn inputFields() { Ok(ResolvedValue::null()) }
338    fn specifiedByURL() { Ok(ResolvedValue::null()) }
339}
340
341impl_resolver! {
342    for DirectiveResolver<'_>:
343
344    __typename = "__Directive";
345
346    fn name(&self_) {
347        Ok(ResolvedValue::leaf(self_.def.name.as_str()))
348    }
349
350    fn description(&self_) {
351        Ok(ResolvedValue::leaf(self_.def.description.as_deref()))
352    }
353
354    fn args(&self_, args) {
355        let include_deprecated = include_deprecated(args);
356        Ok(ResolvedValue::list(self_
357            .def
358            .arguments
359            .iter()
360            .filter(move |def| {
361                include_deprecated || def.directives.get("deprecated").is_none()
362            })
363            .map(|def| {
364                ResolvedValue::object(InputValueResolver { schema: self_.schema, def })
365            })
366        ))
367    }
368
369    fn locations(&self_) {
370        Ok(ResolvedValue::list(self_.def.locations.iter().map(|loc| {
371            ResolvedValue::leaf(loc.name())
372        })))
373    }
374
375    fn isRepeatable(&self_) {
376        Ok(ResolvedValue::leaf(self_.def.repeatable))
377    }
378}
379
380impl_resolver! {
381    for FieldResolver<'_>:
382
383    __typename = "__Field";
384
385    fn name(&self_) {
386        Ok(ResolvedValue::leaf(self_.def.name.as_str()))
387    }
388
389    fn description(&self_) {
390        Ok(ResolvedValue::leaf(self_.def.description.as_deref()))
391    }
392
393    fn args(&self_, args) {
394        let include_deprecated = include_deprecated(args);
395        Ok(ResolvedValue::list(self_
396            .def
397            .arguments
398            .iter()
399            .filter(move |def| {
400                include_deprecated || def.directives.get("deprecated").is_none()
401            })
402            .map(|def| {
403                ResolvedValue::object(InputValueResolver { schema: self_.schema, def })
404            })
405        ))
406    }
407
408    fn type(&self_) {
409        Ok(ty(self_.schema, &self_.def.ty))
410    }
411
412    fn isDeprecated(&self_) {
413        Ok(ResolvedValue::leaf(self_.def.directives.get("deprecated").is_some()))
414    }
415
416    fn deprecationReason(&self_) {
417        Ok(deprecation_reason(&self_.schema, self_.def.directives.get("deprecated")))
418    }
419}
420
421impl_resolver! {
422    for EnumValueResolver<'_>:
423
424    __typename = "__EnumValue";
425
426    fn name(&self_) {
427        Ok(ResolvedValue::leaf(self_.def.value.as_str()))
428    }
429
430    fn description(&self_) {
431        Ok(ResolvedValue::leaf(self_.def.description.as_deref()))
432    }
433
434    fn isDeprecated(&self_) {
435        Ok(ResolvedValue::leaf(self_.def.directives.get("deprecated").is_some()))
436    }
437
438    fn deprecationReason(&self_) {
439        Ok(deprecation_reason(&self_.schema, self_.def.directives.get("deprecated")))
440    }
441}
442
443impl_resolver! {
444    for InputValueResolver<'_>:
445
446    __typename = "__InputValue";
447
448    fn name(&self_) {
449        Ok(ResolvedValue::leaf(self_.def.name.as_str()))
450    }
451
452    fn description(&self_) {
453        Ok(ResolvedValue::leaf(self_.def.description.as_deref()))
454    }
455
456    fn type(&self_) {
457        Ok(ty(self_.schema, &self_.def.ty))
458    }
459
460    fn defaultValue(&self_) {
461        Ok(ResolvedValue::leaf(self_.def.default_value.as_ref().map(|val| {
462            val.serialize().no_indent().to_string()
463        })))
464    }
465
466    fn isDeprecated(&self_) {
467        Ok(ResolvedValue::leaf(self_.def.directives.get("deprecated").is_some()))
468    }
469
470    fn deprecationReason(&self_) {
471        Ok(deprecation_reason(&self_.schema, self_.def.directives.get("deprecated")))
472    }
473}
474
475/// Although it should be non-null, the `includeDeprecated: Boolean = false` argument is nullable
476fn include_deprecated(args: &JsonMap) -> bool {
477    match &args["includeDeprecated"] {
478        serde_json_bytes::Value::Bool(b) => *b,
479        serde_json_bytes::Value::Null => false,
480        _ => unreachable!(),
481    }
482}