1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! See the docs for "juniper-from-schema" for more info about this.

#![deny(
    unused_imports,
    mutable_borrow_reservation_conflict,
    dead_code,
    unused_variables,
    unused_must_use
)]
#![recursion_limit = "128"]
#![doc(html_root_url = "https://docs.rs/juniper-from-schema-code-gen/0.3.2")]

extern crate proc_macro;
extern crate proc_macro2;

mod ast_pass;
mod nullable_type;
mod parse_input;
mod pretty_print;

use self::{
    ast_pass::{ast_data_pass::AstData, error::Error, CodeGenPass},
    parse_input::{default_context_type, default_error_type, GraphqlSchemaFromFileInput},
};
use graphql_parser::parse_schema;
use proc_macro2::TokenStream;
use std::collections::BTreeSet;
use syn::Type;

/// Read a GraphQL schema file and generate corresponding Juniper macro calls.
///
/// See [the crate level docs](index.html) for an example.
#[proc_macro]
pub fn graphql_schema_from_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let parsed = match syn::parse::<GraphqlSchemaFromFileInput>(input) {
        Ok(p) => p,
        Err(e) => return e.to_compile_error().into(),
    };

    match std::fs::read_to_string(&parsed.schema_path) {
        Ok(schema) => parse_and_gen_schema(&schema, parsed.error_type, parsed.context_type),
        Err(err) => panic!("{}", err),
    }
}

/// Write your GraphQL schema directly in your Rust code.
///
/// This is mostly useful for testing. Prefer using [`graphql_schema_from_file`][] for larger
/// schemas.
///
/// [`graphql_schema_from_file`]: macro.graphql_schema_from_file.html
///
/// # Example
///
/// ```ignore
/// graphql_schema! {
///     schema {
///         query: Query
///     }
///
///     type Query {
///         helloWorld: String! @juniper(ownership: "owned")
///     }
/// }
///
/// pub struct Query;
///
/// impl QueryFields for Query {
///     fn field_hello_world(
///         &self,
///         executor: &Executor<'_, Context>,
///     ) -> FieldResult<String> {
///         Ok("Hello, World!".to_string())
///     }
/// }
/// ```
#[proc_macro]
pub fn graphql_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: TokenStream = input.into();
    let schema = input.to_string();
    parse_and_gen_schema(&schema, default_error_type(), default_context_type())
}

fn parse_and_gen_schema(
    schema: &str,
    error_type: Type,
    context_type: Type,
) -> proc_macro::TokenStream {
    let doc = match parse_schema(&schema) {
        Ok(doc) => doc,
        Err(parse_error) => panic!("{}", parse_error),
    };

    let ast_data = match AstData::new_from_schema_and_doc(schema, &doc) {
        Ok(x) => x,
        Err(errors) => print_and_panic_if_errors(errors),
    };

    let output = CodeGenPass::new(schema, error_type, context_type, ast_data);

    match output.gen_juniper_code(&doc) {
        Ok(tokens) => {
            let out: proc_macro::TokenStream = tokens.into();

            if debugging_enabled() {
                self::pretty_print::code_gen_debug(out.to_string());
            }

            out
        }
        Err(errors) => print_and_panic_if_errors(errors),
    }
}

fn print_and_panic_if_errors<T>(errors: BTreeSet<Error>) -> T {
    let count = errors.len();

    let out = errors
        .into_iter()
        .map(|error| error.to_string())
        .collect::<Vec<_>>()
        .join("\n\n");

    if count == 1 {
        panic!("\n\n{}\n\naborting due to previous error\n", out)
    } else {
        panic!("\n\n{}\n\naborting due to {} errors\n", out, count)
    }
}

fn debugging_enabled() -> bool {
    if let Ok(val) = std::env::var("JUNIPER_FROM_SCHEMA_DEBUG") {
        if &val == "1" {
            return true;
        }
    }

    false
}