libgraphql_core/operation/
executable_document_builder.rs

1use crate::ast;
2use crate::file_reader;
3use crate::operation::ExecutableDocument;
4use crate::operation::FragmentRegistry;
5use crate::operation::Operation;
6use crate::operation::OperationBuildError;
7use crate::operation::OperationBuilder;
8use crate::schema::Schema;
9use std::path::Path;
10use std::sync::Arc;
11use thiserror::Error;
12
13type Result<T> = std::result::Result<T, ExecutableDocumentBuildError>;
14
15pub struct ExecutableDocumentBuilder<'schema: 'fragreg, 'fragreg> {
16    fragment_registry: &'fragreg FragmentRegistry<'schema>,
17    operations: Vec<Operation<'schema, 'fragreg>>,
18    schema: &'schema Schema,
19}
20
21impl<'schema, 'fragreg> ExecutableDocumentBuilder<'schema, 'fragreg> {
22    pub fn build(self) -> Result<ExecutableDocument<'schema, 'fragreg>> {
23        Ok(ExecutableDocument {
24            fragment_registry: self.fragment_registry,
25            operations: self.operations,
26            schema: self.schema,
27        })
28    }
29
30    pub fn new(
31        schema: &'schema Schema,
32        fragment_registry: &'fragreg FragmentRegistry<'schema>,
33    ) -> Self {
34        Self {
35            fragment_registry,
36            operations: vec![],
37            schema,
38        }
39    }
40
41    pub fn from_ast(
42        schema: &'schema Schema,
43        fragment_registry: &'fragreg FragmentRegistry<'schema>,
44        ast: &ast::operation::Document,
45        file_path: Option<&Path>,
46    ) -> Result<Self> {
47        let mut operation_build_errors = vec![];
48        let mut operations = vec![];
49        for def in &ast.definitions {
50            use ast::operation::Definition as Def;
51            match def {
52                Def::Fragment(_frag_def) => {
53                    // TODO(!!!)
54                },
55
56                Def::Operation(op_def) => {
57                    // NOTE: We intentionally do not enforce the following rule
58                    //       defined in the GraphQL spec stating that if a
59                    //       document contains multiple operations, all
60                    //       operations in that document must be named:
61                    //
62                    //       https://spec.graphql.org/October2021/#sel-EAFPTCAACCkEr-Y
63                    //
64                    //       This is easy to detect if/when it's relevant to a
65                    //       use-case, but it's quite limiting to enforce at
66                    //       this layer for many other use-cases
67                    //       (e.g. batch processing tools, etc).
68                    let mut maybe_op = OperationBuilder::from_ast(
69                        schema,
70                        fragment_registry,
71                        op_def,
72                        file_path,
73                    ).and_then(|op_builder| op_builder.build());
74
75                    if let Err(errs) = &mut maybe_op {
76                        operation_build_errors.append(errs);
77                        continue;
78                    }
79                    operations.push(maybe_op.unwrap())
80                },
81            }
82        }
83
84        if !operation_build_errors.is_empty() {
85            return Err(ExecutableDocumentBuildError::OperationBuildErrors(
86                operation_build_errors,
87            ));
88        }
89
90        Ok(Self {
91            fragment_registry,
92            schema,
93            operations,
94        })
95    }
96
97    pub fn from_file(
98        schema: &'schema Schema,
99        fragment_registry: &'fragreg FragmentRegistry<'schema>,
100        file_path: impl AsRef<Path>,
101    ) -> Result<Self> {
102        let file_path = file_path.as_ref();
103        let file_content = file_reader::read_content(file_path)
104            .map_err(|e| ExecutableDocumentBuildError::ExecutableDocumentFileReadError(
105                Box::new(e),
106            ))?;
107        Self::from_str(schema, fragment_registry, file_content, Some(file_path))
108    }
109
110    pub fn from_str(
111        schema: &'schema Schema,
112        fragment_registry: &'fragreg FragmentRegistry<'schema>,
113        content: impl AsRef<str>,
114        file_path: Option<&Path>,
115    ) -> Result<Self> {
116        let ast_doc = ast::operation::parse(content.as_ref())?;
117        Self::from_ast(schema, fragment_registry, &ast_doc, file_path)
118    }
119}
120
121#[derive(Clone, Debug, Error)]
122pub enum ExecutableDocumentBuildError {
123    #[error(
124        "Failure while trying to read an executable document file from disk: $0"
125    )]
126    ExecutableDocumentFileReadError(Box<file_reader::ReadContentError>),
127
128    #[error(
129        "Encountered errors while building operations within this \
130        executable document: {0:?}",
131    )]
132    OperationBuildErrors(Vec<OperationBuildError>),
133
134    #[error("Error parsing executable document: $0")]
135    ParseError(Arc<ast::operation::ParseError>),
136}
137impl std::convert::From<ast::operation::ParseError> for ExecutableDocumentBuildError {
138    fn from(value: ast::operation::ParseError) -> Self {
139        Self::ParseError(Arc::new(value))
140    }
141}