rusty_gql/
operation.rs

1use std::{
2    collections::HashMap,
3    ops::Deref,
4    sync::{Arc, Mutex},
5};
6
7use graphql_parser::{
8    query::{Definition, Document, FragmentDefinition, SelectionSet, VariableDefinition},
9    schema::Directive,
10};
11
12use crate::{error::GqlError, Variables};
13
14#[derive(Debug)]
15pub struct OperationInner<'a> {
16    pub operation_type: OperationType,
17    pub directives: Vec<Directive<'a, String>>,
18    pub variable_definitions: Vec<VariableDefinition<'a, String>>,
19    pub selection_set: SelectionSet<'a, String>,
20    pub fragment_definitions: HashMap<String, FragmentDefinition<'a, String>>,
21    pub errors: Mutex<Vec<GqlError>>,
22    pub variables: Variables,
23}
24
25#[derive(Debug)]
26pub struct Operation<'a>(Arc<OperationInner<'a>>);
27
28impl<'a> Operation<'a> {
29    pub fn new(operation: OperationInner<'a>) -> Operation<'a> {
30        Operation(Arc::new(operation))
31    }
32}
33
34impl<'a> Deref for Operation<'a> {
35    type Target = OperationInner<'a>;
36
37    fn deref(&self) -> &Self::Target {
38        &self.0
39    }
40}
41
42#[derive(Clone, Debug)]
43struct OperationDefinition<'a> {
44    operation_type: OperationType,
45    directives: Vec<Directive<'a, String>>,
46    variable_definitions: Vec<VariableDefinition<'a, String>>,
47    selection_set: SelectionSet<'a, String>,
48}
49
50#[derive(Clone, Debug)]
51pub enum OperationType {
52    Query,
53    Mutation,
54    Subscription,
55}
56
57impl ToString for OperationType {
58    fn to_string(&self) -> String {
59        match self {
60            OperationType::Query => String::from("Query"),
61            OperationType::Mutation => String::from("Mutation"),
62            OperationType::Subscription => String::from("Subscription"),
63        }
64    }
65}
66
67pub fn get_operation_definitions<'a>(
68    doc: &'a Document<'a, String>,
69) -> Vec<&'a graphql_parser::query::Definition<'a, String>> {
70    doc.definitions
71        .iter()
72        .filter(|def| matches!(def, Definition::Operation(_)))
73        .collect::<Vec<_>>()
74}
75
76pub fn build_operation<'a>(
77    doc: &'a Document<'a, String>,
78    operation_name: Option<String>,
79    variables: Variables,
80) -> Result<Operation<'a>, GqlError> {
81    let mut fragment_definitions = HashMap::new();
82
83    for def in &doc.definitions {
84        if let Definition::Fragment(fragment) = def {
85            let name = fragment.name.to_string();
86            fragment_definitions.insert(name, fragment.to_owned());
87        }
88    }
89
90    if operation_name.is_none() && get_operation_definitions(doc).len() > 1 {
91        return Err(GqlError::new(
92            "Must provide operation name if multiple operation exist",
93            None,
94        ));
95    };
96
97    let mut operation_definitions: HashMap<String, OperationDefinition> = HashMap::new();
98    let no_name_key = "no_operation_name";
99
100    for definition in doc.clone().definitions {
101        if let Definition::Operation(operation) = definition {
102            match operation {
103                graphql_parser::query::OperationDefinition::SelectionSet(selection_set) => {
104                    operation_definitions.insert(
105                        no_name_key.to_string(),
106                        OperationDefinition {
107                            operation_type: OperationType::Query,
108                            selection_set,
109                            directives: vec![],
110                            variable_definitions: vec![],
111                        },
112                    );
113                }
114                graphql_parser::query::OperationDefinition::Query(query) => {
115                    let query_name = query.name.unwrap_or_else(|| no_name_key.to_string());
116                    operation_definitions.insert(
117                        query_name,
118                        OperationDefinition {
119                            operation_type: OperationType::Query,
120                            selection_set: query.selection_set,
121                            directives: query.directives,
122                            variable_definitions: query.variable_definitions,
123                        },
124                    );
125                }
126                graphql_parser::query::OperationDefinition::Mutation(mutation) => {
127                    let mutation_name = mutation.name.unwrap_or_else(|| no_name_key.to_string());
128                    operation_definitions.insert(
129                        mutation_name,
130                        OperationDefinition {
131                            operation_type: OperationType::Mutation,
132                            selection_set: mutation.selection_set,
133                            directives: mutation.directives,
134                            variable_definitions: mutation.variable_definitions,
135                        },
136                    );
137                }
138                graphql_parser::query::OperationDefinition::Subscription(subscription) => {
139                    let subscription_name =
140                        subscription.name.unwrap_or_else(|| no_name_key.to_string());
141                    operation_definitions.insert(
142                        subscription_name,
143                        OperationDefinition {
144                            operation_type: OperationType::Subscription,
145                            selection_set: subscription.selection_set,
146                            directives: subscription.directives,
147                            variable_definitions: subscription.variable_definitions,
148                        },
149                    );
150                }
151            }
152        }
153    }
154
155    match operation_name {
156        Some(name) => {
157            let target_def = operation_definitions.get(name.as_str());
158            match target_def {
159                Some(definition) => {
160                    let definition = definition.clone();
161                    Ok(Operation(Arc::new(OperationInner {
162                        operation_type: definition.operation_type,
163                        fragment_definitions,
164                        directives: definition.directives,
165                        variable_definitions: definition.variable_definitions,
166                        selection_set: definition.selection_set,
167                        errors: Default::default(),
168                        variables,
169                    })))
170                }
171                None => Err(GqlError::new(
172                    format!("operationName: {} is not contained in query", name),
173                    None,
174                )),
175            }
176        }
177        None => match operation_definitions.get(&no_name_key.to_string()) {
178            Some(definition) => {
179                let definition = definition.clone();
180                Ok(Operation(Arc::new(OperationInner {
181                    operation_type: definition.operation_type,
182                    fragment_definitions,
183                    directives: definition.directives,
184                    variable_definitions: definition.variable_definitions,
185                    selection_set: definition.selection_set,
186                    errors: Default::default(),
187                    variables,
188                })))
189            }
190            None => match operation_definitions.values().next() {
191                Some(definition) => {
192                    let definition = definition.clone();
193                    Ok(Operation(Arc::new(OperationInner {
194                        operation_type: definition.operation_type,
195                        fragment_definitions,
196                        directives: definition.directives,
197                        variable_definitions: definition.variable_definitions,
198                        selection_set: definition.selection_set,
199                        errors: Default::default(),
200                        variables,
201                    })))
202                }
203                None => Err(GqlError::new("operation does not exist", None)),
204            },
205        },
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use crate::operation::build_operation;
212
213    #[test]
214    fn build_single_operation() {
215        let parsed_query =
216            graphql_parser::parse_query::<String>(r#"query GetPerson { persons { name age } }"#)
217                .unwrap();
218
219        let operation = build_operation(&parsed_query, None, Default::default());
220        assert!(operation.is_ok());
221        assert_eq!(operation.unwrap().operation_type.to_string(), "Query");
222    }
223
224    #[test]
225    fn build_multiple_operation() {
226        let parsed_query = graphql_parser::parse_query::<String>(
227            r#"query GetPerson { persons { name age } } query GetPet { pets { name kind } }"#,
228        )
229        .unwrap();
230
231        let operation = build_operation(
232            &parsed_query,
233            Some("GetPerson".to_string()),
234            Default::default(),
235        );
236        assert!(operation.is_ok());
237        assert_eq!(operation.unwrap().operation_type.to_string(), "Query");
238    }
239
240    #[test]
241    fn fails_build_multiple_operation_without_operation_name() {
242        let parsed_query = graphql_parser::parse_query::<String>(
243            r#"query GetPerson { persons { name age } } query GetPet { pets { name kind } }"#,
244        )
245        .unwrap();
246
247        let operation = build_operation(&parsed_query, None, Default::default());
248        assert!(operation.is_err());
249    }
250}