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}