graphql_id/
lib.rs

1extern crate crypto;
2extern crate graphql_parser;
3extern crate itertools;
4
5pub mod error;
6pub mod traverse;
7pub mod visitor;
8
9mod wrappers;
10
11use crypto::digest::Digest;
12use crypto::sha2::Sha256;
13use error::*;
14use graphql_parser::query::*;
15use itertools::FoldWhile::{Continue, Done};
16use itertools::Itertools;
17use std::collections::BTreeSet;
18use std::result::Result::*;
19use traverse::*;
20use visitor::Visitor;
21use wrappers::FragmentDefinitionContainer;
22
23struct FragmentSpreadVisitor<'a> {
24    /// the document reference to get to fragment node from fragment spread
25    document: &'a Document,
26
27    /// used fragments - our result that we will use to select the fragments
28    used_fragments: Vec<&'a FragmentDefinition>,
29
30    /// the visited fragments during traversal - so as to avoid infinite
31    /// recursion of fragments
32    visited_fragments: Vec<&'a FragmentDefinition>,
33}
34
35impl<'a> Visitor for FragmentSpreadVisitor<'a> {
36    fn visit_fragment_spread_enter(
37        &mut self,
38        fragment_spread: &FragmentSpread,
39    ) -> Result<(), GraphQLError> {
40        let fragment = self
41            .document
42            .definitions
43            .iter()
44            .filter_map(|definition| filter_fragment_defintion(definition))
45            .find(|fragment_definition| fragment_definition.name == fragment_spread.fragment_name)
46            .ok_or(GraphQLError::FragmentNotFound)?;
47
48        let found = &self
49            .visited_fragments
50            .iter()
51            .find(|visited| &visited.name == &fragment.name);
52
53        if let Some(fragment) = found {
54            return Err(GraphQLError::InfiniteFragmentRecursionError(
55                fragment.name.clone(),
56            ));
57        }
58
59        let mut used_fragments: BTreeSet<_> = self
60            .used_fragments
61            .iter()
62            .map(|fragment| FragmentDefinitionContainer(fragment))
63            .collect();
64        used_fragments.insert(FragmentDefinitionContainer(fragment));
65
66        let used_fragments = used_fragments
67            .iter()
68            .map(|container| container.0)
69            .collect::<Vec<_>>();
70
71        let mut sub_visitor = FragmentSpreadVisitor {
72            document: &self.document,
73            visited_fragments: [&self.visited_fragments[..], &[fragment]].concat(),
74            used_fragments: used_fragments.clone(),
75        };
76
77        self.used_fragments = used_fragments;
78
79        let mut traversal = Traversal {
80            visitor: &mut sub_visitor,
81        };
82
83        traversal.handle_fragment_definition(&fragment)?;
84
85        Ok(())
86    }
87}
88
89/// Generate an operation id for a query with an operation_name
90///
91/// # Examples
92///
93/// ```
94///     use graphql_id::*;
95///     let query = "
96///         query Foo { foo }
97///     ";
98///
99///     generate_operation_id(&query, &"Foo");
100/// ```
101///
102pub fn generate_operation_id(query: &str, operation_name: &str) -> Result<String, GraphQLError> {
103    let mut hasher = Sha256::new();
104    let operation = select_operation(query, operation_name)?;
105    hasher.input_str(operation.as_str());
106
107    Ok(hasher.result_str())
108}
109
110/// Generate an operation id for the default operation in the query string
111///
112/// # Examples
113///
114/// ```
115///     use graphql_id::*;
116///     let query = "
117///         query Foo { foo }
118///     ";
119///
120///     generate_default_operation_id(&query);
121/// ```
122pub fn generate_default_operation_id(query: &str) -> Result<String, GraphQLError> {
123    let operation_name = get_default_operation_name(query)?;
124
125    generate_operation_id(query, operation_name.as_str())
126}
127
128/// Get the default operation name from the query
129///
130/// # Examples
131///
132/// ```
133///     use graphql_id::*;
134///     let query = "
135///         query Foo { foo }
136///     ";
137///     get_default_operation_name(&query); // -> Foo
138/// ```
139///
140/// # Errors
141///
142/// - AnonymousOperation - Currently, this library does not support anonymous operations
143///     - example: `query { foo }` or `{ foo }`
144/// - MultipleOperation - When more than one operations are found, there is no default operation.
145///     - example: `query A { a } query B { b }`
146///
147pub fn get_default_operation_name(query: &str) -> Result<String, GraphQLError> {
148    let document = parse_query(query)?;
149
150    document
151        .definitions
152        .iter()
153        .filter_map(|definition| filter_operation_definition(definition))
154        .fold_while(
155            Err(GraphQLError::OperationNotFound),
156            |result, operation_definition| match operation_definition {
157                OperationDefinition::Query(query) => {
158                    if result.is_ok() {
159                        Done(Err(GraphQLError::MultipleOperation))
160                    } else {
161                        Continue(query.clone().name.ok_or(GraphQLError::AnonymousOperation))
162                    }
163                }
164                OperationDefinition::Mutation(mutation) => {
165                    if result.is_ok() {
166                        Done(Err(GraphQLError::MultipleOperation))
167                    } else {
168                        Continue(
169                            mutation
170                                .clone()
171                                .name
172                                .ok_or(GraphQLError::AnonymousOperation),
173                        )
174                    }
175                }
176                OperationDefinition::SelectionSet(_) => Done(Err(GraphQLError::AnonymousOperation)),
177                // TODO: handle subscription
178                OperationDefinition::Subscription(_) => Done(Err(GraphQLError::AnonymousOperation)),
179            },
180        )
181        .into_inner()
182}
183
184/// Select an operation in the query and remove the unused fragments and other operations
185///
186/// # Examples
187///
188/// ```
189///     use graphql_id::*;
190///     let query = "
191///         query A { foo }
192///         query B { bar }
193///     ";
194///     select_operation(&query, &"A"); // -> "query A { foo }"
195/// ```
196pub fn select_operation(query: &str, operation_name: &str) -> Result<String, GraphQLError> {
197    let document = parse_query(query)?;
198    let operation = select_operation_definition(&document, &operation_name)
199        .ok_or(GraphQLError::OperationNotFound)?;
200
201    let mut visitor = FragmentSpreadVisitor {
202        document: &document,
203        used_fragments: vec![],
204        visited_fragments: vec![],
205    };
206
207    // this block is required so the mutable borrow of visitor ends here
208    {
209        let mut traversal = Traversal {
210            visitor: &mut visitor,
211        };
212        traversal.handle_operation_definition(&operation)?;
213    }
214
215    let fragments = visitor
216        .used_fragments
217        .iter()
218        .map(|fragment| format!("{}", fragment))
219        .collect::<Vec<String>>()
220        .join("\n");
221
222    Ok(format!("{}{}", operation, fragments))
223}
224
225fn select_operation_definition<'a>(
226    document: &'a Document,
227    operation_name: &'a str,
228) -> Option<&'a OperationDefinition> {
229    document
230        .definitions
231        .iter()
232        .filter_map(|definition| filter_operation_definition(definition))
233        .find(|operation_definition| match operation_definition {
234            OperationDefinition::Query(query) => is_operation_name(&query.name, &operation_name),
235            OperationDefinition::Mutation(mutation) => {
236                is_operation_name(&mutation.name, &operation_name)
237            }
238            _ => false,
239        })
240}
241
242fn filter_operation_definition(definition: &Definition) -> Option<&OperationDefinition> {
243    match definition {
244        Definition::Operation(operation_definition) => Some(&operation_definition),
245        _ => None,
246    }
247}
248
249fn filter_fragment_defintion(definition: &Definition) -> Option<&FragmentDefinition> {
250    match definition {
251        Definition::Fragment(fragment_definition) => Some(&fragment_definition),
252        _ => None,
253    }
254}
255
256fn is_operation_name(name: &Option<String>, operation_name: &str) -> bool {
257    name.as_ref().map_or(false, |name| name == operation_name)
258}