apollo_smith/
schema.rs

1use crate::description::Description;
2use crate::directive::Directive;
3use crate::directive::DirectiveLocation;
4use crate::name::Name;
5use crate::ty::Ty;
6use crate::DocumentBuilder;
7use apollo_compiler::ast;
8use apollo_compiler::Node;
9use arbitrary::Result as ArbitraryResult;
10use indexmap::IndexMap;
11
12/// A GraphQL service’s collective type system capabilities are referred to as that service’s “schema”.
13///
14/// *SchemaDefinition*:
15///     Description? **schema** Directives? **{** RootOperationTypeDefinition* **}**
16///
17/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Schema).
18#[derive(Debug, Clone)]
19pub struct SchemaDef {
20    pub(crate) description: Option<Description>,
21    pub(crate) directives: IndexMap<Name, Directive>,
22    pub(crate) query: Option<Ty>,
23    pub(crate) mutation: Option<Ty>,
24    pub(crate) subscription: Option<Ty>,
25    pub(crate) extend: bool,
26}
27
28impl From<SchemaDef> for ast::Definition {
29    fn from(x: SchemaDef) -> Self {
30        let root_operations = [
31            (ast::OperationType::Query, x.query),
32            (ast::OperationType::Mutation, x.mutation),
33            (ast::OperationType::Subscription, x.subscription),
34        ]
35        .into_iter()
36        .flat_map(|(ty, name)| name.map(|n| Node::new((ty, n.name().into()))))
37        .collect();
38        if x.extend {
39            ast::SchemaExtension {
40                directives: Directive::to_ast(x.directives),
41                root_operations,
42            }
43            .into()
44        } else {
45            ast::SchemaDefinition {
46                description: x.description.map(Into::into),
47                directives: Directive::to_ast(x.directives),
48                root_operations,
49            }
50            .into()
51        }
52    }
53}
54
55impl TryFrom<apollo_parser::cst::SchemaDefinition> for SchemaDef {
56    type Error = crate::FromError;
57
58    fn try_from(schema_def: apollo_parser::cst::SchemaDefinition) -> Result<Self, Self::Error> {
59        let mut query = None;
60        let mut mutation = None;
61        let mut subcription = None;
62        for root_op in schema_def.root_operation_type_definitions() {
63            let op_type = root_op.operation_type().unwrap();
64            let named_type = root_op.named_type().unwrap();
65            if op_type.query_token().is_some() {
66                query = named_type.into();
67            } else if op_type.mutation_token().is_some() {
68                mutation = named_type.into();
69            } else if op_type.subscription_token().is_some() {
70                subcription = named_type.into();
71            } else {
72                panic!("operation type must be one of query|mutation|subscription");
73            }
74        }
75        Ok(Self {
76            description: schema_def.description().map(Description::from),
77            directives: schema_def
78                .directives()
79                .map(Directive::convert_directives)
80                .transpose()?
81                .unwrap_or_default(),
82            query: query.map(Ty::from),
83            mutation: mutation.map(Ty::from),
84            subscription: subcription.map(Ty::from),
85            extend: false,
86        })
87    }
88}
89
90impl TryFrom<apollo_parser::cst::SchemaExtension> for SchemaDef {
91    type Error = crate::FromError;
92
93    fn try_from(schema_def: apollo_parser::cst::SchemaExtension) -> Result<Self, Self::Error> {
94        let mut query = None;
95        let mut mutation = None;
96        let mut subcription = None;
97        for root_op in schema_def.root_operation_type_definitions() {
98            let op_type = root_op.operation_type().unwrap();
99            let named_type = root_op.named_type().unwrap();
100            if op_type.query_token().is_some() {
101                query = named_type.into();
102            } else if op_type.mutation_token().is_some() {
103                mutation = named_type.into();
104            } else if op_type.subscription_token().is_some() {
105                subcription = named_type.into();
106            } else {
107                panic!("operation type must be one of query|mutation|subscription");
108            }
109        }
110        Ok(Self {
111            description: None,
112            directives: schema_def
113                .directives()
114                .map(Directive::convert_directives)
115                .transpose()?
116                .unwrap_or_default(),
117            query: query.map(Ty::from),
118            mutation: mutation.map(Ty::from),
119            subscription: subcription.map(Ty::from),
120            extend: true,
121        })
122    }
123}
124
125impl DocumentBuilder<'_> {
126    /// Create an arbitrary `SchemaDef`
127    pub fn schema_definition(&mut self) -> ArbitraryResult<SchemaDef> {
128        let description = self
129            .u
130            .arbitrary()
131            .unwrap_or(false)
132            .then(|| self.description())
133            .transpose()?;
134        let directives = self.directives(DirectiveLocation::Schema)?;
135        let named_types: Vec<Ty> = self
136            .list_existing_object_types()
137            .into_iter()
138            .filter(|ty| ty.is_named() && !ty.is_builtin())
139            .collect();
140
141        let arbitrary_idx: usize = self.u.arbitrary::<usize>()?;
142
143        let mut query = arbitrary_idx
144            .is_multiple_of(2)
145            .then(|| self.u.choose(&named_types))
146            .transpose()?
147            .cloned();
148        let mut mutation = arbitrary_idx
149            .is_multiple_of(3)
150            .then(|| self.u.choose(&named_types))
151            .transpose()?
152            .cloned();
153        let mut subscription = arbitrary_idx
154            .is_multiple_of(5)
155            .then(|| self.u.choose(&named_types))
156            .transpose()?
157            .cloned();
158        // If no one has been filled
159        if let (None, None, None) = (&query, &mutation, &subscription) {
160            let arbitrary_op_type_idx = self.u.int_in_range(0..=2usize)?;
161            match arbitrary_op_type_idx {
162                0 => query = Some(self.u.choose(&named_types)?.clone()),
163                1 => mutation = Some(self.u.choose(&named_types)?.clone()),
164                2 => subscription = Some(self.u.choose(&named_types)?.clone()),
165                _ => unreachable!(),
166            }
167        }
168
169        Ok(SchemaDef {
170            description,
171            directives,
172            query,
173            mutation,
174            subscription,
175            extend: self.u.arbitrary().unwrap_or(false),
176        })
177    }
178}