apollo_smith/
operation.rs

1use crate::directive::Directive;
2use crate::directive::DirectiveLocation;
3use crate::name::Name;
4use crate::selection_set::SelectionSet;
5use crate::variable::VariableDef;
6use crate::DocumentBuilder;
7use apollo_compiler::ast;
8use apollo_compiler::Node;
9use arbitrary::Arbitrary;
10use arbitrary::Result as ArbitraryResult;
11use indexmap::IndexMap;
12
13/// The __operationDef type represents an operation definition
14///
15/// *OperationDefinition*:
16///     OperationType Name? VariableDefinitions? Directives? SelectionSet
17///
18/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Operations).
19#[derive(Debug, Clone)]
20pub struct OperationDef {
21    pub(crate) operation_type: OperationType,
22    pub(crate) name: Option<Name>,
23    pub(crate) variable_definitions: Vec<VariableDef>,
24    pub(crate) directives: IndexMap<Name, Directive>,
25    pub(crate) selection_set: SelectionSet,
26}
27
28impl From<OperationDef> for ast::Definition {
29    fn from(x: OperationDef) -> Self {
30        ast::OperationDefinition {
31            operation_type: x.operation_type.into(),
32            name: x.name.map(Into::into),
33            directives: Directive::to_ast(x.directives),
34            variables: x
35                .variable_definitions
36                .into_iter()
37                .map(|x| Node::new(x.into()))
38                .collect(),
39            selection_set: x.selection_set.into(),
40        }
41        .into()
42    }
43}
44
45impl From<OperationDef> for String {
46    fn from(op_def: OperationDef) -> Self {
47        ast::Definition::from(op_def).to_string()
48    }
49}
50
51impl TryFrom<apollo_parser::cst::OperationDefinition> for OperationDef {
52    type Error = crate::FromError;
53
54    fn try_from(
55        operation_def: apollo_parser::cst::OperationDefinition,
56    ) -> Result<Self, Self::Error> {
57        Ok(Self {
58            name: operation_def.name().map(Name::from),
59            directives: operation_def
60                .directives()
61                .map(Directive::convert_directives)
62                .transpose()?
63                .unwrap_or_default(),
64            operation_type: operation_def
65                .operation_type()
66                .map(OperationType::from)
67                .unwrap_or(OperationType::Query),
68            variable_definitions: Vec::new(),
69            selection_set: operation_def.selection_set().unwrap().try_into()?,
70        })
71    }
72}
73
74/// The __operationType type represents the kind of operation
75///
76/// *OperationType*:
77///     query | mutation | subscription
78///
79/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#OperationType).
80#[derive(Debug, Arbitrary, Clone, Copy, PartialEq, Eq)]
81pub enum OperationType {
82    Query,
83    Mutation,
84    Subscription,
85}
86
87impl From<OperationType> for ast::OperationType {
88    fn from(op_type: OperationType) -> Self {
89        match op_type {
90            OperationType::Query => Self::Query,
91            OperationType::Mutation => Self::Mutation,
92            OperationType::Subscription => Self::Subscription,
93        }
94    }
95}
96
97impl From<apollo_parser::cst::OperationType> for OperationType {
98    fn from(op_type: apollo_parser::cst::OperationType) -> Self {
99        if op_type.query_token().is_some() {
100            Self::Query
101        } else if op_type.mutation_token().is_some() {
102            Self::Mutation
103        } else if op_type.subscription_token().is_some() {
104            Self::Subscription
105        } else {
106            Self::Query
107        }
108    }
109}
110
111impl DocumentBuilder<'_> {
112    /// Create an arbitrary `OperationDef` taking the last `SchemaDef`
113    pub fn operation_definition(&mut self) -> ArbitraryResult<Option<OperationDef>> {
114        let schema = match self.schema_def.clone() {
115            Some(schema_def) => schema_def,
116            None => return Ok(None),
117        };
118        let name = self
119            .u
120            .arbitrary()
121            .unwrap_or(false)
122            .then(|| self.type_name())
123            .transpose()?;
124        let available_operations = {
125            let mut ops = vec![];
126            if let Some(query) = &schema.query {
127                ops.push((OperationType::Query, query));
128            }
129            if let Some(mutation) = &schema.mutation {
130                ops.push((OperationType::Mutation, mutation));
131            }
132            if let Some(subscription) = &schema.subscription {
133                ops.push((OperationType::Subscription, subscription));
134            }
135
136            ops
137        };
138
139        let (operation_type, chosen_ty) = self.u.choose(&available_operations)?;
140        let directive_location = match operation_type {
141            OperationType::Query => DirectiveLocation::Query,
142            OperationType::Mutation => DirectiveLocation::Mutation,
143            OperationType::Subscription => DirectiveLocation::Subscription,
144        };
145        let directives = self.directives(directive_location)?;
146
147        // Stack
148        self.stack_ty(chosen_ty);
149
150        let selection_set = self.selection_set()?;
151
152        self.stack.pop();
153        // Clear the chosen arguments for an operation
154        self.chosen_arguments.clear();
155        // Clear the chosen aliases for field in an operation
156        self.chosen_aliases.clear();
157
158        assert!(
159            self.stack.is_empty(),
160            "the stack must be empty at the end of an operation definition"
161        );
162
163        // TODO
164        let variable_definitions = vec![];
165
166        Ok(Some(OperationDef {
167            operation_type: *operation_type,
168            name,
169            variable_definitions,
170            directives,
171            selection_set,
172        }))
173    }
174}