cynic_introspection/schema/
mod.rs

1use crate::query::DirectiveLocation;
2
3#[cfg(feature = "sdl")]
4mod sdl;
5
6impl crate::query::IntrospectionQuery {
7    /// Converts the results of an IntrospectionQuery into a `Schema`,
8    /// which has some stronger types than those offered by the introspection query
9    pub fn into_schema(self) -> Result<Schema, SchemaError> {
10        Schema::try_from(
11            self.introspected_schema
12                .ok_or(SchemaError::IntrospectionQueryFailed)?,
13        )
14    }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[non_exhaustive]
19/// A GraphQL schema
20pub struct Schema {
21    /// The name of the `query` root operation type
22    pub query_type: String,
23    /// The name of the `mutation` root operation type
24    pub mutation_type: Option<String>,
25    /// The name of the `subscription` root operation type
26    pub subscription_type: Option<String>,
27    /// All the `Type`s available in the schema
28    pub types: Vec<Type>,
29    /// All the `Directive`s available in the schema
30    pub directives: Vec<Directive>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35/// A directive either used in the schema or available to queries
36pub struct Directive {
37    /// The name of the directive
38    pub name: String,
39    /// A description of the directive
40    pub description: Option<String>,
41    /// Any arguments that can be provided to the directive
42    pub args: Vec<InputValue>,
43    /// Whether the directive is repeatable or not
44    pub is_repeatable: bool,
45    /// The locations where the directive may be used
46    pub locations: Vec<DirectiveLocation>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50/// A type defined in a `Schema`
51pub enum Type {
52    /// The type is an `ObjectType`
53    Object(ObjectType),
54    /// The type is an `InputObjectType`
55    InputObject(InputObjectType),
56    /// The type is an `EnumType`
57    Enum(EnumType),
58    /// The type is an `InterfaceType`
59    Interface(InterfaceType),
60    /// The type is a `UnionType`
61    Union(UnionType),
62    /// The type is a `Scalar`
63    Scalar(ScalarType),
64}
65
66impl Type {
67    /// The name of the type
68    pub fn name(&self) -> &str {
69        match self {
70            Type::Object(inner) => &inner.name,
71            Type::InputObject(inner) => &inner.name,
72            Type::Enum(inner) => &inner.name,
73            Type::Interface(inner) => &inner.name,
74            Type::Union(inner) => &inner.name,
75            Type::Scalar(inner) => &inner.name,
76        }
77    }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81/// GraphQL Objects represent a list of named fields, each of which
82/// yield a value of a specific type.
83pub struct ObjectType {
84    /// The name of the object
85    pub name: String,
86    /// A description of the object
87    pub description: Option<String>,
88    /// The fields of the object
89    pub fields: Vec<Field>,
90    /// Any interfaces this object implements
91    pub interfaces: Vec<String>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95#[non_exhaustive]
96/// A GraphQL Input Object defines a set of input fields; the input fields are
97/// either scalars, enums, or other input objects
98pub struct InputObjectType {
99    /// The name of the input object
100    pub name: String,
101    /// A description of the input object
102    pub description: Option<String>,
103    /// The fields of the input object
104    pub fields: Vec<InputValue>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[non_exhaustive]
109/// GraphQL Enum types represent leaf values in a GraphQL type system which
110/// can have one of a set of possible values.
111pub struct EnumType {
112    /// The name of the enum
113    pub name: String,
114    /// A description of the enum
115    pub description: Option<String>,
116    /// The possible values this enum can have
117    pub values: Vec<EnumValue>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121#[non_exhaustive]
122/// Interfaces are an abstract type where there are common fields declared.
123pub struct InterfaceType {
124    /// The name of the interface
125    pub name: String,
126    /// A description of the interface
127    pub description: Option<String>,
128    /// The fields of the interface
129    pub fields: Vec<Field>,
130    /// Any interfaces this interface also implements
131    pub interfaces: Vec<String>,
132    /// The set of types that implement this interface
133    pub possible_types: Vec<String>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137#[non_exhaustive]
138/// GraphQL Unions represent objects that could be any of a list of GraphQL
139/// Object types.
140pub struct UnionType {
141    /// The name of the union
142    pub name: String,
143    /// A description of the union
144    pub description: Option<String>,
145    /// The set of types that this interface could be
146    pub possible_types: Vec<String>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150#[non_exhaustive]
151/// Scalar types represent primitive leaf values in a GraphQL type system.
152///
153/// GraphQL provides a number of built-in scalars, however type systems may also
154/// add additional custom scalars to introduce additional semantic meaning.
155pub struct ScalarType {
156    /// The name of the scalar
157    pub name: String,
158    /// A description of the scalar
159    pub description: Option<String>,
160    /// A URL pointing to a specification for this scalar, if there is one
161    pub specified_by_url: Option<String>,
162}
163
164impl ScalarType {
165    /// Returns true if this scalar represents one of the [built in scalars][1].
166    ///
167    /// [1]: http://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars
168    pub fn is_builtin(&self) -> bool {
169        matches!(
170            self.name.as_str(),
171            "String" | "Boolean" | "Int" | "ID" | "Float"
172        )
173    }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177#[non_exhaustive]
178/// One of the possible values of an [EnumType]
179pub struct EnumValue {
180    /// The name of the enum
181    pub name: String,
182    /// A description of the value
183    pub description: Option<String>,
184    /// Whether this value is deprecated and should no longer be used.
185    pub deprecated: Deprecated,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
189/// Whether a [Field] or [EnumValue] is deprecated.
190pub enum Deprecated {
191    /// The [Field] or [EnumValue] is not deprecated.
192    No,
193    /// The [Field] or [EnumValue] is deprecated, with an optional reason
194    Yes(Option<String>),
195}
196
197impl Deprecated {
198    fn new(is_deprecated: bool, deprecation_reason: Option<String>) -> Deprecated {
199        match (is_deprecated, deprecation_reason) {
200            (false, _) => Deprecated::No,
201            (true, reason) => Deprecated::Yes(reason),
202        }
203    }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207#[non_exhaustive]
208/// One of the fields of an [ObjectType] or [InterfaceType]
209pub struct Field {
210    /// The name of the field
211    pub name: String,
212    /// A description of the field
213    pub description: Option<String>,
214    /// A list of arguments this field accepts.
215    pub args: Vec<InputValue>,
216    /// The type of value returned by this field
217    pub ty: FieldType,
218    /// Whether this field is deprecated and should no longer be used.
219    pub deprecated: Deprecated,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223#[non_exhaustive]
224/// Represents field and directive arguments as well as the fields of an input object.
225pub struct InputValue {
226    /// The name of the argument or field
227    pub name: String,
228    /// A description of the argument or field
229    pub description: Option<String>,
230    /// The type of this argument or field
231    pub ty: FieldType,
232    /// An optional default value for this field, represented as a GraphQL literal
233    pub default_value: Option<String>,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
237#[non_exhaustive]
238/// The type of a [Field] or [InputValue]
239///
240/// Provides a [std::fmt::Display] impl that can be used to get the GraphQL string
241/// representation of this [FieldType].
242pub struct FieldType {
243    /// The "wrapping types" for a field - whether it is wrapped in NonNull or a List
244    pub wrapping: FieldWrapping,
245    /// The named type contained within any `FieldWrapping`
246    pub name: String,
247}
248
249impl std::fmt::Display for FieldType {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        let FieldType { wrapping, name } = self;
252        let wrapping_types = wrapping.into_iter().collect::<Vec<_>>();
253        for wrapping_type in &wrapping_types {
254            match wrapping_type {
255                WrappingType::List => write!(f, "[")?,
256                WrappingType::NonNull => {}
257            }
258        }
259        write!(f, "{name}")?;
260        for wrapping_type in wrapping_types.iter().rev() {
261            match wrapping_type {
262                WrappingType::List => write!(f, "]")?,
263                WrappingType::NonNull => write!(f, "!")?,
264            }
265        }
266        Ok(())
267    }
268}
269
270#[derive(Clone, Copy, PartialEq, Eq)]
271/// A list of [WrappingType]s that wrap a [FieldType]
272pub struct FieldWrapping([u8; 8]);
273
274impl std::fmt::Debug for FieldWrapping {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(f, "{:?}", self.into_iter().collect::<Vec<_>>())
277    }
278}
279
280impl IntoIterator for FieldWrapping {
281    type Item = WrappingType;
282
283    type IntoIter = Box<dyn Iterator<Item = WrappingType>>;
284
285    fn into_iter(self) -> Self::IntoIter {
286        Box::new(
287            self.0
288                .into_iter()
289                .take_while(|&item| item != 0)
290                .flat_map(|item| match item {
291                    1 => std::iter::once(WrappingType::List),
292                    2 => std::iter::once(WrappingType::NonNull),
293                    _ => unreachable!(),
294                }),
295        )
296    }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300/// Types that can wrap either named types or other wrapper types
301pub enum WrappingType {
302    /// Wraps a named or wrapper type in a list
303    ///
304    /// This type can be nested to form lists, or combined with [WrappingType::NonNull]
305    /// to define non nullable lists.
306    List,
307    /// Marks the wrapped type as non nullable.
308    NonNull,
309}
310
311#[derive(thiserror::Error, Debug, Eq, PartialEq)]
312/// An error that can occur when building the schema
313pub enum SchemaError {
314    /// A type in the introspection output was missing a name
315    #[error("A type in the introspection output was missing a name")]
316    TypeMissingName,
317    /// Found a list wrapper type in a position that should contain a named type
318    #[error("Found a list wrapper type in a position that should contain a named type")]
319    UnexpectedList,
320    /// Found a non-null wrapper type in a position that should contain a named type
321    #[error("Found a non-null wrapper type in a position that should contain a named type")]
322    UnexpectedNonNull,
323    /// Found a wrapping type that had no inner ofType
324    #[error("Found a wrapping type that had no inner ofType")]
325    WrappingTypeWithNoInner,
326    /// Found a wrapping type that was too nested
327    #[error("Found a wrapping type that was too nested")]
328    TooMuchWrapping,
329    /// The introspection query didn't return results
330    #[error("The introspection query returned no data.  Try looking in the response for errors")]
331    IntrospectionQueryFailed,
332}
333
334impl TryFrom<crate::query::IntrospectedSchema> for Schema {
335    type Error = SchemaError;
336
337    fn try_from(schema: crate::query::IntrospectedSchema) -> Result<Self, Self::Error> {
338        Ok(Schema {
339            query_type: schema.query_type.into_name()?,
340            mutation_type: schema.mutation_type.map(|ty| ty.into_name()).transpose()?,
341            subscription_type: schema
342                .subscription_type
343                .map(|ty| ty.into_name())
344                .transpose()?,
345            types: schema
346                .types
347                .into_iter()
348                .map(TryInto::try_into)
349                .collect::<Result<Vec<_>, _>>()?,
350            directives: schema
351                .directives
352                .into_iter()
353                .map(TryInto::try_into)
354                .collect::<Result<Vec<_>, _>>()?,
355        })
356    }
357}
358
359impl TryFrom<crate::query::Type> for Type {
360    type Error = SchemaError;
361
362    fn try_from(ty: crate::query::Type) -> Result<Self, Self::Error> {
363        match ty.kind {
364            crate::query::TypeKind::Scalar => Ok(Type::Scalar(ScalarType {
365                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
366                description: ty.description,
367                specified_by_url: None,
368            })),
369            crate::query::TypeKind::Object => Ok(Type::Object(ObjectType {
370                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
371                fields: ty
372                    .fields
373                    .unwrap_or_default()
374                    .into_iter()
375                    .map(TryInto::try_into)
376                    .collect::<Result<Vec<_>, _>>()?,
377                description: ty.description,
378                interfaces: ty
379                    .interfaces
380                    .unwrap_or_default()
381                    .into_iter()
382                    .map(|ty| ty.into_name())
383                    .collect::<Result<Vec<_>, _>>()?,
384            })),
385            crate::query::TypeKind::Interface => Ok(Type::Interface(InterfaceType {
386                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
387                description: ty.description,
388                fields: ty
389                    .fields
390                    .unwrap_or_default()
391                    .into_iter()
392                    .map(TryInto::try_into)
393                    .collect::<Result<Vec<_>, _>>()?,
394                interfaces: ty
395                    .interfaces
396                    .unwrap_or_default()
397                    .into_iter()
398                    .map(|ty| ty.into_name())
399                    .collect::<Result<Vec<_>, _>>()?,
400                possible_types: ty
401                    .possible_types
402                    .unwrap_or_default()
403                    .into_iter()
404                    .map(|ty| ty.into_name())
405                    .collect::<Result<Vec<_>, _>>()?,
406            })),
407            crate::query::TypeKind::Union => Ok(Type::Union(UnionType {
408                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
409                description: ty.description,
410                possible_types: ty
411                    .possible_types
412                    .unwrap_or_default()
413                    .into_iter()
414                    .map(|ty| ty.into_name())
415                    .collect::<Result<Vec<_>, _>>()?,
416            })),
417            crate::query::TypeKind::Enum => Ok(Type::Enum(EnumType {
418                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
419                description: ty.description,
420                values: ty
421                    .enum_values
422                    .unwrap_or_default()
423                    .into_iter()
424                    .map(Into::into)
425                    .collect(),
426            })),
427            crate::query::TypeKind::InputObject => Ok(Type::InputObject(InputObjectType {
428                name: ty.name.ok_or(SchemaError::TypeMissingName)?,
429                description: ty.description,
430                fields: ty
431                    .input_fields
432                    .unwrap_or_default()
433                    .into_iter()
434                    .map(InputValue::try_from)
435                    .collect::<Result<Vec<_>, _>>()?,
436            })),
437            crate::query::TypeKind::List => Err(SchemaError::UnexpectedList),
438            crate::query::TypeKind::NonNull => Err(SchemaError::UnexpectedNonNull),
439        }
440    }
441}
442
443impl TryFrom<crate::query::Field> for Field {
444    type Error = SchemaError;
445
446    fn try_from(field: crate::query::Field) -> Result<Self, Self::Error> {
447        Ok(Field {
448            name: field.name,
449            description: field.description,
450            args: field
451                .args
452                .into_iter()
453                .map(TryInto::try_into)
454                .collect::<Result<Vec<_>, _>>()?,
455            ty: field.ty.try_into()?,
456            deprecated: Deprecated::new(field.is_deprecated, field.deprecation_reason),
457        })
458    }
459}
460
461impl TryFrom<crate::query::FieldType> for FieldType {
462    type Error = SchemaError;
463
464    fn try_from(field_type: crate::query::FieldType) -> Result<Self, Self::Error> {
465        let mut wrapping = [0; 8];
466        let mut wrapping_pos = 0;
467        let mut current_ty = field_type;
468        loop {
469            if wrapping_pos >= wrapping.len() {
470                return Err(SchemaError::TooMuchWrapping);
471            }
472            match current_ty.kind {
473                crate::query::TypeKind::List => {
474                    wrapping[wrapping_pos] = 1;
475                    wrapping_pos += 1;
476                    current_ty = *current_ty
477                        .of_type
478                        .ok_or(SchemaError::WrappingTypeWithNoInner)?;
479                }
480                crate::query::TypeKind::NonNull => {
481                    wrapping[wrapping_pos] = 2;
482                    wrapping_pos += 1;
483                    current_ty = *current_ty
484                        .of_type
485                        .ok_or(SchemaError::WrappingTypeWithNoInner)?;
486                }
487                _ => {
488                    return Ok(FieldType {
489                        name: current_ty.name.ok_or(SchemaError::TypeMissingName)?,
490                        wrapping: FieldWrapping(wrapping),
491                    })
492                }
493            };
494        }
495    }
496}
497
498impl TryFrom<crate::query::InputValue> for InputValue {
499    type Error = SchemaError;
500
501    fn try_from(value: crate::query::InputValue) -> Result<Self, Self::Error> {
502        Ok(InputValue {
503            name: value.name,
504            description: value.description,
505            ty: value.ty.try_into()?,
506            default_value: value.default_value,
507        })
508    }
509}
510
511impl From<crate::query::EnumValue> for EnumValue {
512    fn from(value: crate::query::EnumValue) -> Self {
513        EnumValue {
514            name: value.name,
515            description: value.description,
516            deprecated: Deprecated::new(value.is_deprecated, value.deprecation_reason),
517        }
518    }
519}
520
521impl TryFrom<crate::query::Directive> for Directive {
522    type Error = SchemaError;
523
524    fn try_from(value: crate::query::Directive) -> Result<Self, Self::Error> {
525        Ok(Directive {
526            name: value.name,
527            description: value.description,
528            args: value
529                .args
530                .into_iter()
531                .map(TryInto::try_into)
532                .collect::<Result<Vec<_>, _>>()?,
533            locations: value.locations,
534            is_repeatable: value.is_repeatable,
535        })
536    }
537}
538
539impl crate::query::NamedType {
540    fn into_name(self) -> Result<String, SchemaError> {
541        self.name.ok_or(SchemaError::TypeMissingName)
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548    use crate::query;
549
550    #[test]
551    fn test_field_type_to_string() {
552        let ty = FieldType::try_from(query::FieldType {
553            kind: query::TypeKind::NonNull,
554            name: None,
555            of_type: Some(Box::new(query::FieldType {
556                kind: query::TypeKind::List,
557                name: None,
558                of_type: Some(Box::new(query::FieldType {
559                    kind: query::TypeKind::Scalar,
560                    name: Some("Int".into()),
561                    of_type: None,
562                })),
563            })),
564        })
565        .unwrap();
566
567        assert_eq!(ty.to_string(), "[Int]!");
568
569        let ty = FieldType::try_from(query::FieldType {
570            kind: query::TypeKind::Object,
571            name: Some("MyObject".into()),
572            of_type: None,
573        })
574        .unwrap();
575
576        assert_eq!(ty.to_string(), "MyObject");
577    }
578}