Skip to main content

apollo_smith/
operation.rs

1use crate::directive::Directive;
2use crate::directive::DirectiveLocation;
3use crate::name::Name;
4use crate::selection_set::Selection;
5use crate::selection_set::SelectionSet;
6use crate::variable::VariableDef;
7use crate::DocumentBuilder;
8use apollo_compiler::ast;
9use apollo_compiler::Node;
10use arbitrary::Arbitrary;
11use arbitrary::Result as ArbitraryResult;
12use indexmap::IndexMap;
13
14/// The __operationDef type represents an operation definition
15///
16/// *OperationDefinition*:
17///     OperationType Name? VariableDefinitions? Directives? SelectionSet
18///
19/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Operations).
20#[derive(Debug, Clone)]
21pub struct OperationDef {
22    pub(crate) operation_type: OperationType,
23    pub(crate) name: Option<Name>,
24    pub(crate) variable_definitions: Vec<VariableDef>,
25    pub(crate) directives: IndexMap<Name, Directive>,
26    pub(crate) selection_set: SelectionSet,
27}
28
29impl From<OperationDef> for ast::Definition {
30    fn from(x: OperationDef) -> Self {
31        ast::OperationDefinition {
32            operation_type: x.operation_type.into(),
33            name: x.name.map(Into::into),
34            directives: Directive::to_ast(x.directives),
35            variables: x
36                .variable_definitions
37                .into_iter()
38                .map(|x| Node::new(x.into()))
39                .collect(),
40            selection_set: x.selection_set.into(),
41        }
42        .into()
43    }
44}
45
46impl From<OperationDef> for String {
47    fn from(op_def: OperationDef) -> Self {
48        ast::Definition::from(op_def).to_string()
49    }
50}
51
52impl TryFrom<apollo_parser::cst::OperationDefinition> for OperationDef {
53    type Error = crate::FromError;
54
55    fn try_from(
56        operation_def: apollo_parser::cst::OperationDefinition,
57    ) -> Result<Self, Self::Error> {
58        Ok(Self {
59            name: operation_def.name().map(Name::from),
60            directives: operation_def
61                .directives()
62                .map(Directive::convert_directives)
63                .transpose()?
64                .unwrap_or_default(),
65            operation_type: operation_def
66                .operation_type()
67                .map(OperationType::from)
68                .unwrap_or(OperationType::Query),
69            variable_definitions: Vec::new(),
70            selection_set: operation_def.selection_set().unwrap().try_into()?,
71        })
72    }
73}
74
75/// The __operationType type represents the kind of operation
76///
77/// *OperationType*:
78///     query | mutation | subscription
79///
80/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#OperationType).
81#[derive(Debug, Arbitrary, Clone, Copy, PartialEq, Eq)]
82pub enum OperationType {
83    Query,
84    Mutation,
85    Subscription,
86}
87
88impl From<OperationType> for ast::OperationType {
89    fn from(op_type: OperationType) -> Self {
90        match op_type {
91            OperationType::Query => Self::Query,
92            OperationType::Mutation => Self::Mutation,
93            OperationType::Subscription => Self::Subscription,
94        }
95    }
96}
97
98impl From<apollo_parser::cst::OperationType> for OperationType {
99    fn from(op_type: apollo_parser::cst::OperationType) -> Self {
100        if op_type.query_token().is_some() {
101            Self::Query
102        } else if op_type.mutation_token().is_some() {
103            Self::Mutation
104        } else if op_type.subscription_token().is_some() {
105            Self::Subscription
106        } else {
107            Self::Query
108        }
109    }
110}
111
112impl DocumentBuilder<'_> {
113    /// Create an arbitrary `OperationDef` taking the last `SchemaDef`.
114    pub fn operation_definition(&mut self) -> ArbitraryResult<Option<OperationDef>> {
115        self.operation_definition_in_document(false)
116    }
117
118    /// Like [`operation_definition`], but forces the operation to be
119    /// named when `require_named` is true. A document with more than
120    /// one operation may not contain an anonymous operation.
121    ///
122    /// Operation Name Uniqueness across the document is upheld by
123    /// `type_name`, which never returns a name that already exists on
124    /// the builder.
125    ///
126    /// See <https://spec.graphql.org/October2021/#sec-Lone-Anonymous-Operation>
127    /// and <https://spec.graphql.org/October2021/#sec-Operation-Name-Uniqueness>.
128    ///
129    /// [`operation_definition`]: Self::operation_definition
130    pub(crate) fn operation_definition_in_document(
131        &mut self,
132        require_named: bool,
133    ) -> ArbitraryResult<Option<OperationDef>> {
134        let schema = match self.schema_def.clone() {
135            Some(schema_def) => schema_def,
136            None => return Ok(None),
137        };
138
139        let want_name = require_named || self.u.arbitrary().unwrap_or(false);
140        let name = if want_name {
141            Some(self.type_name()?)
142        } else {
143            None
144        };
145
146        let available_operations = {
147            let mut ops = vec![];
148            if let Some(query) = &schema.query {
149                ops.push((OperationType::Query, query));
150            }
151            if let Some(mutation) = &schema.mutation {
152                ops.push((OperationType::Mutation, mutation));
153            }
154            if let Some(subscription) = &schema.subscription {
155                ops.push((OperationType::Subscription, subscription));
156            }
157
158            ops
159        };
160
161        let (operation_type, chosen_ty) = self.u.choose(&available_operations)?;
162        let directive_location = match operation_type {
163            OperationType::Query => DirectiveLocation::Query,
164            OperationType::Mutation => DirectiveLocation::Mutation,
165            OperationType::Subscription => DirectiveLocation::Subscription,
166        };
167        let directives = self.directives(directive_location)?;
168
169        // Stack
170        self.stack_ty(chosen_ty);
171
172        let selection_set = if matches!(operation_type, OperationType::Subscription) {
173            // Subscription operations must have exactly one root field
174            // per <https://spec.graphql.org/October2021/#sec-Single-root-field>.
175            // We generate exactly one field to satisfy that condition. The 0-index
176            // is sometimes used to create an alias for the field.
177            SelectionSet {
178                selections: vec![Selection::Field(self.field(0)?)],
179            }
180        } else {
181            self.selection_set()?
182        };
183
184        self.stack.pop();
185        assert!(
186            self.stack.is_empty(),
187            "the stack must be empty at the end of an operation definition"
188        );
189
190        // TODO
191        let variable_definitions = vec![];
192
193        Ok(Some(OperationDef {
194            operation_type: *operation_type,
195            name,
196            variable_definitions,
197            directives,
198            selection_set,
199        }))
200    }
201}