juniper_from_schema_code_gen/
lib.rs

1//! See the docs for "juniper-from-schema" for more info about this.
2
3#![deny(
4    unused_imports,
5    mutable_borrow_reservation_conflict,
6    dead_code,
7    unused_variables,
8    unused_must_use
9)]
10#![recursion_limit = "256"]
11#![doc(html_root_url = "https://docs.rs/juniper-from-schema-code-gen/0.5.2")]
12
13extern crate proc_macro;
14extern crate proc_macro2;
15
16mod ast_pass;
17mod nullable_type;
18mod parse_input;
19mod pretty_print;
20
21use self::{
22    ast_pass::{ast_data_pass::AstData, error::Error, CodeGenPass},
23    parse_input::{default_context_type, default_error_type, GraphqlSchemaFromFileInput},
24};
25use graphql_parser::parse_schema;
26use proc_macro2::{Span, TokenStream};
27use quote::quote;
28use std::{collections::BTreeSet, path::Path};
29use syn::Type;
30
31const DATE_TIME_SCALAR_NAME: &str = "DateTimeUtc";
32const DATE_SCALAR_NAME: &str = "Date";
33const UUID_SCALAR_NAME: &str = "Uuid";
34const URL_SCALAR_NAME: &str = "Url";
35
36/// Read a GraphQL schema file and generate corresponding Juniper macro calls.
37///
38/// See [the crate level docs](index.html) for an example.
39#[proc_macro]
40pub fn graphql_schema_from_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
41    let parsed = match syn::parse::<GraphqlSchemaFromFileInput>(input) {
42        Ok(p) => p,
43        Err(e) => return e.to_compile_error().into(),
44    };
45
46    match std::fs::read_to_string(&parsed.schema_path) {
47        Ok(schema) => {
48            let mut tokens = parse_and_gen_schema(&schema, parsed.error_type, parsed.context_type);
49            include_literal_schema(&mut tokens, &parsed.schema_path);
50            tokens
51        }
52        Err(err) => panic!("{}", err),
53    }
54}
55
56// This should cause the Rust schema to be rebuild even if the user only changes the GraphQL schema
57// file.
58fn include_literal_schema(tokens: &mut proc_macro::TokenStream, schema_path: &Path) {
59    let schema_path = syn::LitStr::new(
60        schema_path
61            .to_str()
62            .expect("Invalid UTF-8 characters in file name"),
63        Span::call_site(),
64    );
65
66    tokens.extend(proc_macro::TokenStream::from(quote! {
67        const _: &str = std::include_str!(#schema_path);
68    }));
69}
70
71/// Write your GraphQL schema directly in your Rust code.
72///
73/// This is mostly useful for testing. Prefer using [`graphql_schema_from_file`][] for larger
74/// schemas.
75///
76/// [`graphql_schema_from_file`]: macro.graphql_schema_from_file.html
77///
78/// # Example
79///
80/// ```ignore
81/// graphql_schema! {
82///     schema {
83///         query: Query
84///     }
85///
86///     type Query {
87///         helloWorld: String! @juniper(ownership: "owned")
88///     }
89/// }
90///
91/// pub struct Query;
92///
93/// impl QueryFields for Query {
94///     fn field_hello_world(
95///         &self,
96///         executor: &Executor<'_, Context>,
97///     ) -> FieldResult<String> {
98///         Ok("Hello, World!".to_string())
99///     }
100/// }
101/// ```
102#[proc_macro]
103pub fn graphql_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
104    let input: TokenStream = input.into();
105    let schema = input.to_string();
106    parse_and_gen_schema(&schema, default_error_type(), default_context_type())
107}
108
109fn parse_and_gen_schema(
110    schema: &str,
111    error_type: Type,
112    context_type: Type,
113) -> proc_macro::TokenStream {
114    let doc = match parse_schema(&schema) {
115        Ok(doc) => doc,
116        Err(parse_error) => panic!("{}", parse_error),
117    };
118
119    let ast_data = match AstData::new_from_schema_and_doc(schema, &doc) {
120        Ok(x) => x,
121        Err(errors) => print_and_panic_if_errors(errors),
122    };
123
124    let output = CodeGenPass::new(schema, error_type, context_type, ast_data);
125
126    match output.gen_juniper_code(&doc) {
127        Ok(tokens) => {
128            let out: proc_macro::TokenStream = tokens.into();
129
130            if debugging_enabled() {
131                self::pretty_print::code_gen_debug(out.to_string());
132            }
133
134            out
135        }
136        Err(errors) => print_and_panic_if_errors(errors),
137    }
138}
139
140fn print_and_panic_if_errors<T>(errors: BTreeSet<Error>) -> T {
141    let count = errors.len();
142
143    let out = errors
144        .into_iter()
145        .map(|error| error.to_string())
146        .collect::<Vec<_>>()
147        .join("\n\n");
148
149    if count == 1 {
150        panic!("\n\n{}\n\naborting due to previous error\n", out)
151    } else {
152        panic!("\n\n{}\n\naborting due to {} errors\n", out, count)
153    }
154}
155
156fn debugging_enabled() -> bool {
157    if let Ok(val) = std::env::var("JUNIPER_FROM_SCHEMA_DEBUG") {
158        if &val == "1" {
159            return true;
160        }
161    }
162
163    false
164}