Ensure Specification(June 2018) Compliance (#631)
* Implemented most test cases from the specification * Unified error handling for all generators - Removed proc-macro-error -> not required -> use syn::Error - Everything below lib.rs uses proc_macro2::TokenStream instead of proc_macro::TokenStream - Replaced error handling in attribute parsers * WIP better error messages for *all* macros * Refactored GraphQLInputObject and minor tweaks - removed support for Scalar within a string ("DefaultScalarValue") - removed unraw function and replaced it with the built-in one - added error messages and return types for all functions within utils - added more constraints to fulfill the GraphQL spec * Fixed test-cases which are not compliant with the specification * Removed unused function * Added constrains, updated error messages, added marker * Added argument rename within impl_graphql and fixed `__` tests * Formatted and cleanup * Added GraphQLTypeAsync for input object * Moved codegen tests to separate module Nightly and stable produce different outputs, thus only test nightly. * Added IsInputType/IsOutputType traits for type checking Co-authored-by: Christian Legnitto <LegNeato@users.noreply.github.com>
This commit is contained in:
parent
358ca27d28
commit
558eae91df
77 changed files with 2581 additions and 1015 deletions
|
@ -6,6 +6,7 @@ members = [
|
||||||
"juniper",
|
"juniper",
|
||||||
"integration_tests/juniper_tests",
|
"integration_tests/juniper_tests",
|
||||||
"integration_tests/async_await",
|
"integration_tests/async_await",
|
||||||
|
"integration_tests/codegen_fail",
|
||||||
"juniper_hyper",
|
"juniper_hyper",
|
||||||
"juniper_iron",
|
"juniper_iron",
|
||||||
"juniper_rocket",
|
"juniper_rocket",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use juniper::{graphql_value, GraphQLError, RootNode, Value};
|
use juniper::{graphql_value, EmptyMutation, EmptySubscription, GraphQLError, RootNode, Value};
|
||||||
|
|
||||||
#[derive(juniper::GraphQLEnum)]
|
#[derive(juniper::GraphQLEnum)]
|
||||||
enum UserKind {
|
enum UserKind {
|
||||||
|
@ -71,24 +71,14 @@ impl Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Mutation;
|
|
||||||
|
|
||||||
#[juniper::graphql_object]
|
|
||||||
impl Mutation {}
|
|
||||||
|
|
||||||
struct Subscription;
|
|
||||||
|
|
||||||
#[juniper::graphql_subscription]
|
|
||||||
impl Subscription {}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn async_simple() {
|
async fn async_simple() {
|
||||||
let schema = RootNode::new(Query, Mutation, Subscription);
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let doc = r#"
|
let doc = r#"
|
||||||
query {
|
query {
|
||||||
fieldSync
|
fieldSync
|
||||||
fieldAsyncPlain
|
fieldAsyncPlain
|
||||||
delayed
|
delayed
|
||||||
user(id: "user1") {
|
user(id: "user1") {
|
||||||
kind
|
kind
|
||||||
name
|
name
|
||||||
|
@ -125,7 +115,7 @@ async fn async_simple() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn async_field_validation_error() {
|
async fn async_field_validation_error() {
|
||||||
let schema = RootNode::new(Query, Mutation, Subscription);
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let doc = r#"
|
let doc = r#"
|
||||||
query {
|
query {
|
||||||
nonExistentField
|
nonExistentField
|
||||||
|
@ -152,24 +142,25 @@ async fn async_field_validation_error() {
|
||||||
assert!(is_validation_error);
|
assert!(is_validation_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
// FIXME: test seems broken by design, re-enable later
|
||||||
async fn resolve_into_stream_validation_error() {
|
// #[tokio::test]
|
||||||
let schema = RootNode::new(Query, Mutation, Subscription);
|
// async fn resolve_into_stream_validation_error() {
|
||||||
let doc = r#"
|
// let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
subscription {
|
// let doc = r#"
|
||||||
nonExistent
|
// subscription {
|
||||||
}
|
// nonExistent
|
||||||
"#;
|
// }
|
||||||
let vars = Default::default();
|
// "#;
|
||||||
let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
|
// let vars = Default::default();
|
||||||
assert!(result.is_err());
|
// let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await;
|
||||||
|
// assert!(result.is_err());
|
||||||
|
|
||||||
let error = result.err().unwrap();
|
// let error = result.err().unwrap();
|
||||||
let is_validation_error = match error {
|
// let is_validation_error = match error {
|
||||||
GraphQLError::ValidationError(_) => true,
|
// GraphQLError::ValidationError(_) => true,
|
||||||
_ => false,
|
// _ => false,
|
||||||
};
|
// };
|
||||||
assert!(is_validation_error);
|
// assert!(is_validation_error);
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
14
integration_tests/codegen_fail/Cargo.toml
Normal file
14
integration_tests/codegen_fail/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "juniper_codegen_tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
juniper = { path = "../../juniper" }
|
||||||
|
futures = "0.3.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = { version = "1" }
|
||||||
|
tokio = { version = "0.2", features = ["rt-core", "time", "macros"] }
|
||||||
|
trybuild = "1.0.25"
|
29
integration_tests/codegen_fail/Makefile.toml
Normal file
29
integration_tests/codegen_fail/Makefile.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[tasks.release]
|
||||||
|
disabled = true
|
||||||
|
[tasks.release-some]
|
||||||
|
disabled = true
|
||||||
|
[tasks.release-local-test]
|
||||||
|
disabled = true
|
||||||
|
[tasks.release-some-local-test]
|
||||||
|
disabled = true
|
||||||
|
[tasks.release-dry-run]
|
||||||
|
disabled = true
|
||||||
|
[tasks.release-some-dry-run]
|
||||||
|
disabled = true
|
||||||
|
|
||||||
|
[tasks.test]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-custom]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-flow]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-multi-flow-phase]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-thread-safe]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-verbose]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.test-with-args]
|
||||||
|
condition = { channels = ["nightly"] }
|
||||||
|
[tasks.ci-coverage-flow]
|
||||||
|
condition = { channels = ["nightly"] }
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[derive(juniper::GraphQLEnum)]
|
||||||
|
pub enum Test {}
|
||||||
|
|
||||||
|
fn main() { }
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: GraphQL enum expects at least one field
|
||||||
|
--> $DIR/derive_no_fields.rs:2:1
|
||||||
|
|
|
||||||
|
2 | pub enum Test {}
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Enums
|
|
@ -0,0 +1,11 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct ObjectA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct Object {
|
||||||
|
field: ObjectA,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,30 @@
|
||||||
|
error[E0277]: the trait bound `ObjectA: juniper::ast::FromInputValue<__S>` is not satisfied
|
||||||
|
--> $DIR/derive_incompatible_object.rs:6:10
|
||||||
|
|
|
||||||
|
6 | #[derive(juniper::GraphQLInputObject)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::ast::FromInputValue<__S>` is not implemented for `ObjectA`
|
||||||
|
|
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `ObjectA: juniper::ast::FromInputValue<__S>` is not satisfied
|
||||||
|
--> $DIR/derive_incompatible_object.rs:6:10
|
||||||
|
|
|
||||||
|
6 | #[derive(juniper::GraphQLInputObject)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::ast::FromInputValue<__S>` is not implemented for `ObjectA`
|
||||||
|
|
|
||||||
|
= note: required by `juniper::ast::FromInputValue::from_input_value`
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the current scope
|
||||||
|
--> $DIR/derive_incompatible_object.rs:6:10
|
||||||
|
|
|
||||||
|
2 | struct ObjectA {
|
||||||
|
| -------------- method `to_input_value` not found for this
|
||||||
|
...
|
||||||
|
6 | #[derive(juniper::GraphQLInputObject)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA`
|
||||||
|
|
|
||||||
|
= help: items from traits can only be used if the trait is implemented and in scope
|
||||||
|
= note: the following trait defines an item `to_input_value`, perhaps you need to implement it:
|
||||||
|
candidate #1: `juniper::ast::ToInputValue`
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: GraphQL input object expects at least one field
|
||||||
|
--> $DIR/derive_no_fields.rs:2:1
|
||||||
|
|
|
||||||
|
2 | struct Object {}
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct Object {
|
||||||
|
#[graphql(name = "__test")]
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
|
--> $DIR/derive_no_underscore.rs:3:15
|
||||||
|
|
|
||||||
|
3 | #[graphql(name = "__test")]
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct Object {
|
||||||
|
test: String,
|
||||||
|
#[graphql(name = "test")]
|
||||||
|
test2: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
error: GraphQL input object does not allow fields with the same name
|
||||||
|
--> $DIR/derive_unique_name.rs:4:5
|
||||||
|
|
|
||||||
|
4 | / #[graphql(name = "test")]
|
||||||
|
5 | | test2: String,
|
||||||
|
| |_________________^
|
||||||
|
|
|
||||||
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Input-Objects
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(ObjA),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
field id(__test: ObjA) -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {
|
||||||
|
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,27 @@
|
||||||
|
error[E0277]: the trait bound `ObjA: juniper::ast::FromInputValue` is not satisfied
|
||||||
|
--> $DIR/impl_argument_no_object.rs:11:1
|
||||||
|
|
|
||||||
|
11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
12 | | field id(__test: ObjA) -> &str {
|
||||||
|
13 | | match *self {
|
||||||
|
14 | | Character::A(_) => "funA",
|
||||||
|
... |
|
||||||
|
20 | | }
|
||||||
|
21 | | });
|
||||||
|
| |___^ the trait `juniper::ast::FromInputValue` is not implemented for `ObjA`
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `ObjA: juniper::ast::FromInputValue` is not satisfied
|
||||||
|
--> $DIR/impl_argument_no_object.rs:11:1
|
||||||
|
|
|
||||||
|
11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
12 | | field id(__test: ObjA) -> &str {
|
||||||
|
13 | | match *self {
|
||||||
|
14 | | Character::A(_) => "funA",
|
||||||
|
... |
|
||||||
|
20 | | }
|
||||||
|
21 | | });
|
||||||
|
| |___^ the trait `juniper::ast::FromInputValue` is not implemented for `ObjA`
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(ObjA),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
field id(__test: String) -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {
|
||||||
|
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,12 @@
|
||||||
|
enum Character {}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
|
||||||
|
field id() -> &str {
|
||||||
|
match *self {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(ObjA),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
field id() -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {
|
||||||
|
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(ObjA),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
field __id() -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {
|
||||||
|
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(ObjA),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||||
|
field id() -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field id() -> &str {
|
||||||
|
match *self {
|
||||||
|
Character::A(_) => "funA",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_resolvers: |_| {
|
||||||
|
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Object {
|
||||||
|
test: String,
|
||||||
|
#[graphql(name = "test")]
|
||||||
|
test2: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
error: GraphQL object does not allow fields with the same name
|
||||||
|
--> $DIR/derive_fields_unique.rs:4:5
|
||||||
|
|
|
||||||
|
4 | / #[graphql(name = "test")]
|
||||||
|
5 | | test2: String,
|
||||||
|
| |_________________^
|
||||||
|
|
|
||||||
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Objects
|
|
@ -0,0 +1,12 @@
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct ObjectA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Object {
|
||||||
|
field: ObjectA,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: GraphQL object expects at least one field
|
||||||
|
--> $DIR/derive_no_fields.rs:2:1
|
||||||
|
|
|
||||||
|
2 | struct Object {}
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Objects
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Object {
|
||||||
|
#[graphql(name = "__test")]
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
|
--> $DIR/derive_no_underscore.rs:3:15
|
||||||
|
|
|
||||||
|
3 | #[graphql(name = "__test")]
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,15 @@
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Obj {
|
||||||
|
field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {
|
||||||
|
fn test(&self, test: Obj) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error[E0277]: the trait bound `Obj: juniper::ast::FromInputValue` is not satisfied
|
||||||
|
--> $DIR/impl_argument_no_object.rs:8:1
|
||||||
|
|
|
||||||
|
8 | #[juniper::graphql_object]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::ast::FromInputValue` is not implemented for `Obj`
|
||||||
|
|
|
||||||
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,14 @@
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {
|
||||||
|
fn test(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
error: GraphQL object does not allow fields with the same name
|
||||||
|
--> $DIR/impl_fields_unique.rs:9:5
|
||||||
|
|
|
||||||
|
9 | / fn test(&self) -> String {
|
||||||
|
10 | | String::new()
|
||||||
|
11 | | }
|
||||||
|
| |_____^
|
||||||
|
|
|
||||||
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Objects
|
|
@ -0,0 +1,19 @@
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||||
|
struct Obj {
|
||||||
|
field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {
|
||||||
|
fn test(&self) -> Obj {
|
||||||
|
Obj {
|
||||||
|
field: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,11 @@
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {
|
||||||
|
#[graphql(arguments(arg(name = "__arg")))]
|
||||||
|
fn test(&self, arg: String) -> String {
|
||||||
|
arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
|
--> $DIR/impl_no_argument_underscore.rs:5:29
|
||||||
|
|
|
||||||
|
5 | #[graphql(arguments(arg(name = "__arg")))]
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,6 @@
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: GraphQL object expects at least one field
|
||||||
|
--> $DIR/impl_no_fields.rs:4:1
|
||||||
|
|
|
||||||
|
4 | impl Object {}
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Objects
|
|
@ -0,0 +1,11 @@
|
||||||
|
struct Object {}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Object {
|
||||||
|
#[graphql(name = "__test")]
|
||||||
|
fn test(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
|
--> $DIR/impl_no_underscore.rs:5:15
|
||||||
|
|
|
||||||
|
5 | #[graphql(name = "__test")]
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,12 @@
|
||||||
|
#[derive(juniper::GraphQLEnum)]
|
||||||
|
pub enum Test {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLUnion)]
|
||||||
|
enum Character {
|
||||||
|
Test(Test),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied
|
||||||
|
--> $DIR/derive_enum_field.rs:7:10
|
||||||
|
|
|
||||||
|
7 | #[derive(juniper::GraphQLUnion)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<__S>` is not implemented for `Test`
|
||||||
|
|
|
||||||
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[derive(juniper::GraphQLUnion)]
|
||||||
|
enum Character {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error: GraphQL union expects at least one field
|
||||||
|
--> $DIR/derive_no_fields.rs:2:1
|
||||||
|
|
|
||||||
|
2 | enum Character {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Unions
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[derive(juniper::GraphQLUnion)]
|
||||||
|
enum Character {
|
||||||
|
A(std::string::String),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
error[E0119]: conflicting implementations of trait `std::convert::From<std::string::String>` for type `Character`:
|
||||||
|
--> $DIR/derive_same_type.rs:1:10
|
||||||
|
|
|
||||||
|
1 | #[derive(juniper::GraphQLUnion)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
| |
|
||||||
|
| first implementation here
|
||||||
|
| conflicting implementation for `Character`
|
||||||
|
|
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
23
integration_tests/codegen_fail/fail/union/impl_enum_field.rs
Normal file
23
integration_tests/codegen_fail/fail/union/impl_enum_field.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#[derive(juniper::GraphQLEnum)]
|
||||||
|
#[graphql(context = ())]
|
||||||
|
pub enum Test {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
Test(Test),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[juniper::graphql_union]
|
||||||
|
impl Character {
|
||||||
|
fn resolve(&self) {
|
||||||
|
match self {
|
||||||
|
Test => match *self {
|
||||||
|
Character::Test(ref h) => Some(h),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
|
||||||
|
--> $DIR/impl_enum_field.rs:12:1
|
||||||
|
|
|
||||||
|
12 | #[juniper::graphql_union]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not implemented for `Test`
|
||||||
|
|
|
||||||
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
|
||||||
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
10
integration_tests/codegen_fail/fail/union/impl_no_fields.rs
Normal file
10
integration_tests/codegen_fail/fail/union/impl_no_fields.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
enum Character {}
|
||||||
|
|
||||||
|
#[juniper::graphql_union]
|
||||||
|
impl Character {
|
||||||
|
fn resolve(&self) {
|
||||||
|
match self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
error: GraphQL union expects at least one field
|
||||||
|
--> $DIR/impl_no_fields.rs:5:5
|
||||||
|
|
|
||||||
|
5 | / fn resolve(&self) {
|
||||||
|
6 | | match self {}
|
||||||
|
7 | | }
|
||||||
|
| |_____^
|
||||||
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Unions
|
|
@ -0,0 +1,32 @@
|
||||||
|
// NOTICE: This can not be tested. Implementing Into<T> for each
|
||||||
|
// variant is not possible since we did not created the
|
||||||
|
// enum. Therefore, it is possible that the enum already has existing
|
||||||
|
// Into<T> implementations.
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
pub struct Test {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Character {
|
||||||
|
A(Test),
|
||||||
|
B(Test),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[juniper::graphql_union]
|
||||||
|
impl Character {
|
||||||
|
fn resolve(&self) {
|
||||||
|
match self {
|
||||||
|
Test => match *self {
|
||||||
|
Character::A(ref h) => Some(h),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Test => match *self {
|
||||||
|
Character::B(ref h) => Some(h),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
38
integration_tests/codegen_fail/src/lib.rs
Normal file
38
integration_tests/codegen_fail/src/lib.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// TODO: [Object] Type Validation: §4 (interfaces) for objects
|
||||||
|
// TODO: [Non-Null] §1 A Non‐Null type must not wrap another Non‐Null type.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use std::{
|
||||||
|
fs::{read_dir, DirEntry},
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> io::Result<()> {
|
||||||
|
if dir.is_dir() {
|
||||||
|
for entry in read_dir(dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
visit_dirs(&path, cb)?;
|
||||||
|
} else {
|
||||||
|
cb(&entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_failing_compiliation() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
let dir = PathBuf::from("fail");
|
||||||
|
|
||||||
|
visit_dirs(dir.as_path(), &|entry: &DirEntry| {
|
||||||
|
if let Some(Some("rs")) = entry.path().extension().map(|os| os.to_str()) {
|
||||||
|
t.compile_fail(entry.path());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ use juniper::{
|
||||||
#[graphql(
|
#[graphql(
|
||||||
name = "MyInput",
|
name = "MyInput",
|
||||||
description = "input descr",
|
description = "input descr",
|
||||||
scalar = "DefaultScalarValue"
|
scalar = DefaultScalarValue
|
||||||
)]
|
)]
|
||||||
struct Input {
|
struct Input {
|
||||||
regular_field: String,
|
regular_field: String,
|
||||||
|
@ -86,7 +86,7 @@ impl<'a> GraphQLType<DefaultScalarValue> for &'a Fake {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||||
#[graphql(scalar = "DefaultScalarValue")]
|
#[graphql(scalar = DefaultScalarValue)]
|
||||||
struct WithLifetime<'a> {
|
struct WithLifetime<'a> {
|
||||||
regular_field: &'a Fake,
|
regular_field: &'a Fake,
|
||||||
}
|
}
|
||||||
|
|
86
integration_tests/juniper_tests/src/codegen/impl_object.rs
Normal file
86
integration_tests/juniper_tests/src/codegen/impl_object.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use juniper::DefaultScalarValue;
|
||||||
|
#[cfg(test)]
|
||||||
|
use juniper::Object;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables};
|
||||||
|
|
||||||
|
pub struct MyObject;
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl MyObject {
|
||||||
|
#[graphql(arguments(arg(name = "test")))]
|
||||||
|
fn test(&self, arg: String) -> String {
|
||||||
|
arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn check_argument_rename() {
|
||||||
|
let doc = format!(
|
||||||
|
r#"
|
||||||
|
{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
fields {{
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
"MyObject"
|
||||||
|
);
|
||||||
|
|
||||||
|
run_type_info_query(&doc, |(_, values)| {
|
||||||
|
assert_eq!(
|
||||||
|
*values,
|
||||||
|
vec![Value::object(
|
||||||
|
vec![
|
||||||
|
("name", Value::scalar("test")),
|
||||||
|
("description", Value::null()),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
async fn run_type_info_query<F>(doc: &str, f: F)
|
||||||
|
where
|
||||||
|
F: Fn((&Object<DefaultScalarValue>, &Vec<Value>)) -> (),
|
||||||
|
{
|
||||||
|
let schema = RootNode::new(
|
||||||
|
MyObject,
|
||||||
|
EmptyMutation::<()>::new(),
|
||||||
|
EmptySubscription::<()>::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (result, errs) = execute(doc, None, &schema, &Variables::new(), &())
|
||||||
|
.await
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
println!("Result: {:#?}", result);
|
||||||
|
|
||||||
|
let type_info = result
|
||||||
|
.as_object_value()
|
||||||
|
.expect("Result is not an object")
|
||||||
|
.get_field_value("__type")
|
||||||
|
.expect("__type field missing")
|
||||||
|
.as_object_value()
|
||||||
|
.expect("__type field not an object value");
|
||||||
|
|
||||||
|
let fields = type_info
|
||||||
|
.get_field_value("fields")
|
||||||
|
.expect("fields field missing")
|
||||||
|
.as_list_value()
|
||||||
|
.expect("fields not a list");
|
||||||
|
|
||||||
|
f((type_info, fields));
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ mod derive_input_object;
|
||||||
mod derive_object;
|
mod derive_object;
|
||||||
mod derive_object_with_raw_idents;
|
mod derive_object_with_raw_idents;
|
||||||
mod derive_union;
|
mod derive_union;
|
||||||
|
mod impl_object;
|
||||||
mod impl_scalar;
|
mod impl_scalar;
|
||||||
mod impl_union;
|
mod impl_union;
|
||||||
mod scalar_value_transparent;
|
mod scalar_value_transparent;
|
||||||
|
|
|
@ -29,6 +29,9 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
|
||||||
|
|
||||||
- Derive macro `GraphQLEnum` supports custom context (see [#621](https://github.com/graphql-rust/juniper/pull/621))
|
- Derive macro `GraphQLEnum` supports custom context (see [#621](https://github.com/graphql-rust/juniper/pull/621))
|
||||||
|
|
||||||
|
- Better error messages for all proc macros (see
|
||||||
|
[#631](https://github.com/graphql-rust/juniper/pull/631)
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
- `juniper::graphiql` has moved to `juniper::http::graphiql`
|
- `juniper::graphiql` has moved to `juniper::http::graphiql`
|
||||||
|
@ -54,6 +57,16 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
|
||||||
add subscription type to `RootNode`,
|
add subscription type to `RootNode`,
|
||||||
add subscription endpoint to `playground_source()`
|
add subscription endpoint to `playground_source()`
|
||||||
|
|
||||||
|
- Putting a scalar type into a string is not allowed anymore, e..g,
|
||||||
|
`#[graphql(scalar = "DefaultScalarValue")]`. Only
|
||||||
|
`#[derive(GraphQLInputObject)]` supported this syntax. The
|
||||||
|
refactoring of GraphQLInputObject allowed to drop the support
|
||||||
|
(see [#631](https://github.com/graphql-rust/juniper/pull/631)).
|
||||||
|
|
||||||
|
- Support for renaming arguments within an GraphQL object
|
||||||
|
`#[graphql(arguments(argA(name = "test")))]`
|
||||||
|
(see [#631](https://github.com/graphql-rust/juniper/pull/631))
|
||||||
|
|
||||||
# [[0.14.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.2)
|
# [[0.14.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.2)
|
||||||
|
|
||||||
- Fix incorrect validation with non-executed operations [#455](https://github.com/graphql-rust/juniper/issues/455)
|
- Fix incorrect validation with non-executed operations [#455](https://github.com/graphql-rust/juniper/issues/455)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{RootNode, Value};
|
use crate::{EmptyMutation, RootNode, Value};
|
||||||
|
|
||||||
#[derive(crate::GraphQLEnumInternal)]
|
#[derive(crate::GraphQLEnumInternal)]
|
||||||
enum UserKind {
|
enum UserKind {
|
||||||
|
@ -70,14 +70,9 @@ impl Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Mutation;
|
|
||||||
|
|
||||||
#[crate::graphql_object_internal]
|
|
||||||
impl Mutation {}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn async_simple() {
|
async fn async_simple() {
|
||||||
let schema = RootNode::new(Query, Mutation, crate::EmptySubscription::new());
|
let schema = RootNode::new(Query, EmptyMutation::new(), crate::EmptySubscription::new());
|
||||||
let doc = r#"
|
let doc = r#"
|
||||||
query {
|
query {
|
||||||
fieldSync
|
fieldSync
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl GraphQLScalar for TestComplexScalar {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLInputObject, Debug)]
|
#[derive(GraphQLInputObject, Debug)]
|
||||||
#[graphql(scalar = "DefaultScalarValue")]
|
#[graphql(scalar = DefaultScalarValue)]
|
||||||
struct TestInputObject {
|
struct TestInputObject {
|
||||||
a: Option<String>,
|
a: Option<String>,
|
||||||
b: Option<Vec<Option<String>>>,
|
b: Option<Vec<Option<String>>>,
|
||||||
|
@ -47,7 +47,7 @@ struct TestInputObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLInputObject, Debug)]
|
#[derive(GraphQLInputObject, Debug)]
|
||||||
#[graphql(scalar = "DefaultScalarValue")]
|
#[graphql(scalar = DefaultScalarValue)]
|
||||||
struct TestNestedInputObject {
|
struct TestNestedInputObject {
|
||||||
na: TestInputObject,
|
na: TestInputObject,
|
||||||
nb: String,
|
nb: String,
|
||||||
|
|
|
@ -179,6 +179,7 @@ pub use crate::{
|
||||||
types::{
|
types::{
|
||||||
async_await::GraphQLTypeAsync,
|
async_await::GraphQLTypeAsync,
|
||||||
base::{Arguments, GraphQLType, TypeKind},
|
base::{Arguments, GraphQLType, TypeKind},
|
||||||
|
marker,
|
||||||
scalars::{EmptyMutation, EmptySubscription, ID},
|
scalars::{EmptyMutation, EmptySubscription, ID},
|
||||||
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
|
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,11 @@ struct Human {
|
||||||
struct MyQuery;
|
struct MyQuery;
|
||||||
|
|
||||||
#[crate::graphql_object_internal(context = MyContext)]
|
#[crate::graphql_object_internal(context = MyContext)]
|
||||||
impl MyQuery {}
|
impl MyQuery {
|
||||||
|
fn test(&self) -> i32 {
|
||||||
|
0 // NOTICE: does not serve a purpose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Schema =
|
type Schema =
|
||||||
RootNode<'static, MyQuery, EmptyMutation<MyContext>, MySubscription, DefaultScalarValue>;
|
RootNode<'static, MyQuery, EmptyMutation<MyContext>, MySubscription, DefaultScalarValue>;
|
||||||
|
|
123
juniper/src/types/marker.rs
Normal file
123
juniper/src/types/marker.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
//! Marker traits for GraphQL types.
|
||||||
|
//!
|
||||||
|
//! This module provide specialized types for GraphQL. To ensure that
|
||||||
|
//! only specification compliant construct compile, these marker
|
||||||
|
//! traits are used. Encountering an error where one of these traits
|
||||||
|
//! is involved implies that the construct is not valid in GraphQL.
|
||||||
|
|
||||||
|
use crate::{GraphQLType, ScalarValue};
|
||||||
|
|
||||||
|
/// Maker object for GraphQL objects.
|
||||||
|
///
|
||||||
|
/// This trait extends the GraphQLType and is only used to mark
|
||||||
|
/// object. During compile this addition information is required to
|
||||||
|
/// prevent unwanted structure compiling. If an object requires this
|
||||||
|
/// trait instead of the GraphQLType, then it explicitly requires an
|
||||||
|
/// GraphQL objects. Other types (scalars, enums, and input objects)
|
||||||
|
/// are not allowed.
|
||||||
|
pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
|
||||||
|
/// An arbitrary function without meaning.
|
||||||
|
///
|
||||||
|
/// May contain compile timed check logic which ensures that types
|
||||||
|
/// are used correctly according to the GraphQL specification.
|
||||||
|
fn mark() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker trait for types which can be used as output types.
|
||||||
|
///
|
||||||
|
/// The GraphQL specification differentiates between input and output
|
||||||
|
/// types. Each type which can be used as an output type should
|
||||||
|
/// implement this trait. The specification defines enum, scalar,
|
||||||
|
/// object, union, and interface as output types.
|
||||||
|
pub trait IsOutputType<S: ScalarValue>: GraphQLType<S> {
|
||||||
|
/// An arbitrary function without meaning.
|
||||||
|
///
|
||||||
|
/// May contain compile timed check logic which ensures that types
|
||||||
|
/// are used correctly according to the GraphQL specification.
|
||||||
|
fn mark() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker trait for types which can be used as input types.
|
||||||
|
///
|
||||||
|
/// The GraphQL specification differentiates between input and output
|
||||||
|
/// types. Each type which can be used as an input type should
|
||||||
|
/// implement this trait. The specification defines enum, scalar, and
|
||||||
|
/// input object input types.
|
||||||
|
pub trait IsInputType<S: ScalarValue>: GraphQLType<S> {
|
||||||
|
/// An arbitrary function without meaning.
|
||||||
|
///
|
||||||
|
/// May contain compile timed check logic which ensures that types
|
||||||
|
/// are used correctly according to the GraphQL specification.
|
||||||
|
fn mark() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> IsInputType<S> for Option<T>
|
||||||
|
where
|
||||||
|
T: IsInputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> IsOutputType<S> for Option<T>
|
||||||
|
where
|
||||||
|
T: IsOutputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> IsOutputType<S> for Vec<T>
|
||||||
|
where
|
||||||
|
T: IsOutputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, T> IsOutputType<S> for &'a [T]
|
||||||
|
where
|
||||||
|
T: IsOutputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> IsInputType<S> for Vec<T>
|
||||||
|
where
|
||||||
|
T: IsInputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, T> IsInputType<S> for &'a [T]
|
||||||
|
where
|
||||||
|
T: IsInputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, T> IsInputType<S> for &T
|
||||||
|
where
|
||||||
|
T: IsInputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl<'a, S, T> IsOutputType<S> for &T
|
||||||
|
where
|
||||||
|
T: IsOutputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> IsInputType<S> for Box<T>
|
||||||
|
where
|
||||||
|
T: IsInputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl<S, T> IsOutputType<S> for Box<T>
|
||||||
|
where
|
||||||
|
T: IsOutputType<S>,
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S> IsInputType<S> for &str where S: ScalarValue {}
|
||||||
|
impl<'a, S> IsOutputType<S> for &str where S: ScalarValue {}
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod async_await;
|
pub mod async_await;
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
|
pub mod marker;
|
||||||
pub mod name;
|
pub mod name;
|
||||||
pub mod pointers;
|
pub mod pointers;
|
||||||
pub mod scalars;
|
pub mod scalars;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use juniper::{
|
use juniper::{
|
||||||
graphql_object, graphql_subscription, DefaultScalarValue, ExecutionError, FieldError,
|
graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError,
|
||||||
GraphQLEnum, Value, Variables,
|
FieldError, GraphQLEnum, Value, Variables,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type QueryResult = Result<
|
pub type QueryResult = Result<
|
||||||
|
@ -37,6 +37,7 @@ pub enum UserKind {
|
||||||
Guest,
|
Guest,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub kind: UserKind,
|
pub kind: UserKind,
|
||||||
|
@ -57,9 +58,6 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_object(Context = Context)]
|
|
||||||
impl User {}
|
|
||||||
|
|
||||||
pub struct Query;
|
pub struct Query;
|
||||||
|
|
||||||
#[graphql_object(Context = Context)]
|
#[graphql_object(Context = Context)]
|
||||||
|
@ -91,18 +89,9 @@ impl Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mutation;
|
pub fn new_schema(
|
||||||
|
) -> juniper::RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription<Context>> {
|
||||||
#[graphql_object(Context = Context)]
|
juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new())
|
||||||
impl Mutation {}
|
|
||||||
|
|
||||||
pub struct Subscription;
|
|
||||||
|
|
||||||
#[graphql_subscription(Context = Context)]
|
|
||||||
impl Subscription {}
|
|
||||||
|
|
||||||
pub fn new_schema() -> juniper::RootNode<'static, Query, Mutation, Subscription> {
|
|
||||||
juniper::RootNode::new(Query, Mutation, Subscription)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_sync(query: &str, vars: Variables) -> QueryResult {
|
pub fn execute_sync(query: &str, vars: Variables) -> QueryResult {
|
||||||
|
|
|
@ -18,8 +18,8 @@ proc-macro = true
|
||||||
proc-macro2 = "1.0.1"
|
proc-macro2 = "1.0.1"
|
||||||
syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] }
|
syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] }
|
||||||
quote = "1.0.3"
|
quote = "1.0.3"
|
||||||
proc-macro-error = "0.3.4"
|
|
||||||
futures = "0.3.1"
|
futures = "0.3.1"
|
||||||
|
proc-macro-error = "1.0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
juniper = { version = "0.14.2", path = "../juniper"}
|
juniper = { version = "0.14.2", path = "../juniper"}
|
||||||
|
|
|
@ -1,109 +1,152 @@
|
||||||
|
use crate::{
|
||||||
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
use crate::util;
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self, Data, Fields};
|
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
|
pub fn impl_enum(
|
||||||
|
ast: syn::DeriveInput,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
let ast_span = ast.span();
|
||||||
|
|
||||||
pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
|
|
||||||
if !ast.generics.params.is_empty() {
|
if !ast.generics.params.is_empty() {
|
||||||
panic!("#[derive(GraphQLEnum) does not support generics or lifetimes");
|
return Err(error.custom_error(ast_span, "does not support generics or lifetimes"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let variants = match ast.data {
|
let variants = match ast.data {
|
||||||
Data::Enum(enum_data) => enum_data.variants,
|
Data::Enum(enum_data) => enum_data.variants,
|
||||||
_ => {
|
_ => return Err(error.custom_error(ast_span, "can only be applied to enums")),
|
||||||
panic!("#[derive(GraphlQLEnum)] may only be applied to enums, not to structs");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse attributes.
|
// Parse attributes.
|
||||||
let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) {
|
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !attrs.interfaces.is_empty() {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLEnum) does not support 'interfaces'");
|
|
||||||
}
|
|
||||||
if attrs.scalar.is_some() {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute 'scalar': #[derive(GraphQLEnum) does not support explicit scalars");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse attributes.
|
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
let name = attrs
|
||||||
|
.name
|
||||||
let mut mapping = std::collections::HashMap::new();
|
.clone()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| ident.unraw().to_string());
|
||||||
|
|
||||||
let fields = variants
|
let fields = variants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|field| {
|
.filter_map(|field| {
|
||||||
|
let span = field.span();
|
||||||
let field_attrs = match util::FieldAttributes::from_attrs(
|
let field_attrs = match util::FieldAttributes::from_attrs(
|
||||||
field.attrs,
|
&field.attrs,
|
||||||
util::FieldAttributeParseMode::Object,
|
util::FieldAttributeParseMode::Object,
|
||||||
) {
|
) {
|
||||||
Ok(attrs) => attrs,
|
Ok(attrs) => attrs,
|
||||||
Err(e) => panic!("Invalid #[graphql] attribute for field: \n{}", e),
|
Err(err) => {
|
||||||
|
proc_macro_error::emit_error!(err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if field_attrs.skip {
|
let field_name = field.ident;
|
||||||
panic!("#[derive(GraphQLEnum)] does not support #[graphql(skip)] on fields");
|
let name = field_attrs
|
||||||
} else {
|
.name
|
||||||
let field_name = field.ident;
|
.clone()
|
||||||
let name = field_attrs
|
.map(SpanContainer::into_inner)
|
||||||
.name
|
.unwrap_or_else(|| util::to_upper_snake_case(&field_name.unraw().to_string()));
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| util::to_upper_snake_case(&field_name.to_string()));
|
|
||||||
|
|
||||||
match mapping.get(&name) {
|
let resolver_code = quote!( #ident::#field_name );
|
||||||
Some(other_field_name) =>
|
|
||||||
panic!(format!("#[derive(GraphQLEnum)] all variants needs to be unique. Another field name `{}` has the same identifier `{}`, thus `{}` can not be named `{}`. One of the fields is manually renamed!", other_field_name, name, field_name, name)),
|
let _type = match field.fields {
|
||||||
None => {
|
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
|
||||||
mapping.insert(name.clone(), field_name.clone());
|
_ => {
|
||||||
}
|
error.custom(
|
||||||
|
field.fields.span(),
|
||||||
|
"all fields of the enum must be unnamed, e.g., None",
|
||||||
|
);
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let resolver_code = quote!( #ident::#field_name );
|
if let Some(skip) = field_attrs.skip {
|
||||||
|
error.unsupported_attribute(skip.span(), UnsupportedAttribute::Skip);
|
||||||
let _type = match field.fields {
|
return None;
|
||||||
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
|
|
||||||
_ => panic!("#[derive(GraphQLEnum)] all fields of the enum must be unnamed"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(util::GraphQLTypeDefinitionField {
|
|
||||||
name,
|
|
||||||
_type,
|
|
||||||
args: Vec::new(),
|
|
||||||
description: field_attrs.description,
|
|
||||||
deprecation: field_attrs.deprecation,
|
|
||||||
resolver_code,
|
|
||||||
is_type_inferred: true,
|
|
||||||
is_async: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") {
|
||||||
|
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
field_name.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = field_attrs.default {
|
||||||
|
error.unsupported_attribute_within(
|
||||||
|
default.span_ident(),
|
||||||
|
UnsupportedAttribute::Default,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(util::GraphQLTypeDefinitionField {
|
||||||
|
name,
|
||||||
|
_type,
|
||||||
|
args: Vec::new(),
|
||||||
|
description: field_attrs.description.map(SpanContainer::into_inner),
|
||||||
|
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
|
||||||
|
resolver_code,
|
||||||
|
is_type_inferred: true,
|
||||||
|
is_async: false,
|
||||||
|
default: None,
|
||||||
|
span,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if fields.len() == 0 {
|
proc_macro_error::abort_if_dirty();
|
||||||
panic!("#[derive(GraphQLEnum)] requires at least one variants");
|
|
||||||
|
if fields.is_empty() {
|
||||||
|
error.not_empty(ast_span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) {
|
||||||
|
Some(duplicates) => error.duplicate(duplicates.iter()),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attrs.interfaces.is_empty() {
|
||||||
|
attrs.interfaces.iter().for_each(|elm| {
|
||||||
|
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(scalar) = attrs.scalar {
|
||||||
|
error.unsupported_attribute(scalar.span_ident(), UnsupportedAttribute::Scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") && !is_internal {
|
||||||
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
let definition = util::GraphQLTypeDefiniton {
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
name,
|
name,
|
||||||
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
||||||
context: attrs.context,
|
context: attrs.context.map(SpanContainer::into_inner),
|
||||||
scalar: None,
|
scalar: None,
|
||||||
description: attrs.description,
|
description: attrs.description.map(SpanContainer::into_inner),
|
||||||
fields,
|
fields,
|
||||||
// NOTICE: only unit variants allow -> no generics possible
|
// NOTICE: only unit variants allow -> no generics possible
|
||||||
generics: syn::Generics::default(),
|
generics: syn::Generics::default(),
|
||||||
interfaces: None,
|
interfaces: None,
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async,
|
no_async: attrs.no_async.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
definition.into_enum_tokens(juniper_crate_name)
|
Ok(definition.into_enum_tokens(juniper_crate_name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,331 +1,152 @@
|
||||||
#![allow(clippy::match_wild_err_arm)]
|
#![allow(clippy::match_wild_err_arm)]
|
||||||
use std::str::FromStr;
|
use crate::{
|
||||||
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
use proc_macro2::{Span, TokenStream};
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{self, parse_quote, Data, DeriveInput, Field, Fields, Ident, Meta, NestedMeta};
|
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
use crate::util::*;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
struct ObjAttrs {
|
|
||||||
name: Option<String>,
|
|
||||||
description: Option<String>,
|
|
||||||
scalar: Option<Ident>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjAttrs {
|
|
||||||
fn from_input(input: &DeriveInput) -> ObjAttrs {
|
|
||||||
let mut res = ObjAttrs::default();
|
|
||||||
|
|
||||||
// Check doc comments for description.
|
|
||||||
res.description = get_doc_comment(&input.attrs);
|
|
||||||
|
|
||||||
// Check attributes for name and description.
|
|
||||||
if let Some(items) = get_graphql_attr(&input.attrs) {
|
|
||||||
for item in items {
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "name", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
if is_valid_name(&*val) {
|
|
||||||
res.name = Some(val);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
|
|
||||||
&*val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "description", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.description = Some(val);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(scalar)) =
|
|
||||||
keyed_item_value(&item, "scalar", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.scalar = Some(Ident::new(&scalar as &str, Span::call_site()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
panic!(format!(
|
|
||||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
|
||||||
item
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ObjFieldAttrs {
|
|
||||||
name: Option<String>,
|
|
||||||
description: Option<String>,
|
|
||||||
default: bool,
|
|
||||||
default_expr: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjFieldAttrs {
|
|
||||||
fn from_input(variant: &Field) -> ObjFieldAttrs {
|
|
||||||
let mut res = ObjFieldAttrs::default();
|
|
||||||
|
|
||||||
// Check doc comments for description.
|
|
||||||
res.description = get_doc_comment(&variant.attrs);
|
|
||||||
|
|
||||||
// Check attributes for name and description.
|
|
||||||
if let Some(items) = get_graphql_attr(&variant.attrs) {
|
|
||||||
for item in items {
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "name", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
if is_valid_name(&*val) {
|
|
||||||
res.name = Some(val);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
|
|
||||||
&*val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "description", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.description = Some(val);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "default", AttributeValidation::Any)
|
|
||||||
{
|
|
||||||
res.default_expr = Some(val);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let NestedMeta::Meta(Meta::Path(ref path)) = item {
|
|
||||||
if path.is_ident("default") {
|
|
||||||
res.default = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!(format!(
|
|
||||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
|
||||||
item
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn impl_input_object(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream {
|
|
||||||
let juniper_path = if is_internal {
|
|
||||||
quote!(crate)
|
|
||||||
} else {
|
|
||||||
quote!(juniper)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub fn impl_input_object(
|
||||||
|
ast: syn::DeriveInput,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
let ast_span = ast.span();
|
||||||
let fields = match ast.data {
|
let fields = match ast.data {
|
||||||
Data::Struct(ref data) => match data.fields {
|
Data::Struct(data) => match data.fields {
|
||||||
Fields::Named(ref named) => named.named.iter().collect::<Vec<_>>(),
|
Fields::Named(named) => named.named,
|
||||||
_ => {
|
_ => {
|
||||||
panic!(
|
return Err(
|
||||||
"#[derive(GraphQLInputObject)] may only be used on regular structs with fields"
|
error.custom_error(ast_span, "all fields must be named, e.g., `test: String`")
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => return Err(error.custom_error(ast_span, "can only be used on structs with fields")),
|
||||||
panic!("#[derive(GraphlQLInputObject)] may only be applied to structs, not to enums");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Parse attributes.
|
||||||
|
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
|
||||||
|
|
||||||
// Parse attributes.
|
// Parse attributes.
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let attrs = ObjAttrs::from_input(ast);
|
let name = attrs
|
||||||
let name = attrs.name.unwrap_or_else(|| ast.ident.to_string());
|
.name
|
||||||
let generics = &ast.generics;
|
.clone()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| ident.to_string());
|
||||||
|
|
||||||
let meta_description = match attrs.description {
|
let fields = fields
|
||||||
Some(descr) => quote! { let meta = meta.description(#descr); },
|
.into_iter()
|
||||||
None => quote! { let meta = meta; },
|
.filter_map(|field| {
|
||||||
};
|
let span = field.span();
|
||||||
|
let field_attrs = match util::FieldAttributes::from_attrs(
|
||||||
let mut meta_fields = TokenStream::new();
|
&field.attrs,
|
||||||
let mut from_inputs = TokenStream::new();
|
util::FieldAttributeParseMode::Object,
|
||||||
let mut to_inputs = TokenStream::new();
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
let (_, ty_generics, _) = generics.split_for_impl();
|
Err(e) => {
|
||||||
|
proc_macro_error::emit_error!(e);
|
||||||
let mut generics = generics.clone();
|
return None;
|
||||||
|
|
||||||
let scalar = if let Some(scalar) = attrs.scalar {
|
|
||||||
scalar
|
|
||||||
} else {
|
|
||||||
generics.params.push(parse_quote!(__S));
|
|
||||||
{
|
|
||||||
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
|
|
||||||
where_clause
|
|
||||||
.predicates
|
|
||||||
.push(parse_quote!(__S: #juniper_path::ScalarValue));
|
|
||||||
}
|
|
||||||
Ident::new("__S", Span::call_site())
|
|
||||||
};
|
|
||||||
|
|
||||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
|
||||||
|
|
||||||
for field in fields {
|
|
||||||
let field_ty = &field.ty;
|
|
||||||
let field_attrs = ObjFieldAttrs::from_input(field);
|
|
||||||
let field_ident = field.ident.as_ref().unwrap();
|
|
||||||
|
|
||||||
// Build value.
|
|
||||||
let name = match field_attrs.name {
|
|
||||||
Some(ref name) => {
|
|
||||||
// Custom name specified.
|
|
||||||
name.to_string()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Note: auto camel casing when no custom name specified.
|
|
||||||
crate::util::to_camel_case(&unraw(&field_ident.to_string()))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let field_description = match field_attrs.description {
|
|
||||||
Some(s) => quote! { let field = field.description(#s); },
|
|
||||||
None => quote! {},
|
|
||||||
};
|
|
||||||
|
|
||||||
let default = {
|
|
||||||
if field_attrs.default {
|
|
||||||
Some(quote! { Default::default() })
|
|
||||||
} else {
|
|
||||||
match field_attrs.default_expr {
|
|
||||||
Some(ref def) => match proc_macro::TokenStream::from_str(def) {
|
|
||||||
Ok(t) => match syn::parse::<syn::Expr>(t) {
|
|
||||||
Ok(e) => {
|
|
||||||
let mut tokens = TokenStream::new();
|
|
||||||
e.to_tokens(&mut tokens);
|
|
||||||
Some(tokens)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _ = e;
|
|
||||||
panic!("#graphql(default = ?) must be a valid Rust expression inside a string");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
let _ = e;
|
|
||||||
panic!("#graphql(default = ?) must be a valid Rust expression inside a string");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let field_ident = field.ident.as_ref().unwrap();
|
||||||
|
let name = match field_attrs.name {
|
||||||
|
Some(ref name) => name.to_string(),
|
||||||
|
None => crate::util::to_camel_case(&field_ident.unraw().to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(span) = field_attrs.skip {
|
||||||
|
error.unsupported_attribute_within(span.span(), UnsupportedAttribute::Skip)
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let create_meta_field = match default {
|
if let Some(span) = field_attrs.deprecation {
|
||||||
Some(ref def) => {
|
error.unsupported_attribute_within(
|
||||||
quote! {
|
span.span_ident(),
|
||||||
let field = registry.arg_with_default::<#field_ty>( #name, &#def, &());
|
UnsupportedAttribute::Deprecation,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
quote! {
|
if name.starts_with("__") {
|
||||||
let field = registry.arg::<#field_ty>(#name, &());
|
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
||||||
}
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
name.span()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
meta_fields.extend(quote! {
|
|
||||||
{
|
|
||||||
#create_meta_field
|
|
||||||
#field_description
|
|
||||||
field
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build from_input clause.
|
let resolver_code = quote!(#field_ident);
|
||||||
|
|
||||||
let from_input_default = match default {
|
let default = field_attrs
|
||||||
Some(ref def) => {
|
.default
|
||||||
quote! {
|
.map(|default| match default.into_inner() {
|
||||||
Some(&&#juniper_path::InputValue::Null) | None if true => #def,
|
Some(expr) => expr.into_token_stream(),
|
||||||
}
|
None => quote! { Default::default() },
|
||||||
}
|
});
|
||||||
None => quote! {},
|
|
||||||
};
|
|
||||||
|
|
||||||
from_inputs.extend(quote!{
|
Some(util::GraphQLTypeDefinitionField {
|
||||||
#field_ident: {
|
name,
|
||||||
// TODO: investigate the unwraps here, they seem dangerous!
|
_type: field.ty,
|
||||||
match obj.get(#name) {
|
args: Vec::new(),
|
||||||
#from_input_default
|
description: field_attrs.description.map(SpanContainer::into_inner),
|
||||||
Some(ref v) => #juniper_path::FromInputValue::from_input_value(v).unwrap(),
|
deprecation: None,
|
||||||
None => {
|
resolver_code,
|
||||||
#juniper_path::FromInputValue::from_input_value(&#juniper_path::InputValue::<#scalar>::null())
|
is_type_inferred: true,
|
||||||
.unwrap()
|
is_async: false,
|
||||||
},
|
default,
|
||||||
}
|
span,
|
||||||
},
|
})
|
||||||
});
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Build to_input clause.
|
proc_macro_error::abort_if_dirty();
|
||||||
to_inputs.extend(quote! {
|
|
||||||
(#name, self.#field_ident.to_input_value()),
|
if fields.is_empty() {
|
||||||
|
error.not_empty(ast_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
match crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) {
|
||||||
|
Some(duplicates) => error.duplicate(duplicates.iter()),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attrs.interfaces.is_empty() {
|
||||||
|
attrs.interfaces.iter().for_each(|elm| {
|
||||||
|
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = quote! {
|
if let Some(duplicates) =
|
||||||
impl#impl_generics #juniper_path::GraphQLType<#scalar> for #ident #ty_generics
|
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
|
||||||
#where_clause
|
{
|
||||||
{
|
error.duplicate(duplicates.iter());
|
||||||
type Context = ();
|
}
|
||||||
type TypeInfo = ();
|
|
||||||
|
|
||||||
fn name(_: &()) -> Option<&'static str> {
|
if name.starts_with("__") && !is_internal {
|
||||||
Some(#name)
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
}
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn meta<'r>(
|
proc_macro_error::abort_if_dirty();
|
||||||
_: &(),
|
|
||||||
registry: &mut #juniper_path::Registry<'r, #scalar>
|
|
||||||
) -> #juniper_path::meta::MetaType<'r, #scalar>
|
|
||||||
where #scalar: 'r
|
|
||||||
{
|
|
||||||
let fields = &[
|
|
||||||
#meta_fields
|
|
||||||
];
|
|
||||||
let meta = registry.build_input_object_type::<#ident>(&(), fields);
|
|
||||||
#meta_description
|
|
||||||
meta.into_meta()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl#impl_generics #juniper_path::FromInputValue<#scalar> for #ident #ty_generics
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
#where_clause
|
name,
|
||||||
{
|
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
||||||
fn from_input_value(value: &#juniper_path::InputValue<#scalar>) -> Option<Self>
|
context: attrs.context.map(SpanContainer::into_inner),
|
||||||
{
|
scalar: attrs.scalar.map(SpanContainer::into_inner),
|
||||||
if let Some(obj) = value.to_object_value() {
|
description: attrs.description.map(SpanContainer::into_inner),
|
||||||
let item = #ident {
|
fields,
|
||||||
#from_inputs
|
generics: ast.generics,
|
||||||
};
|
interfaces: None,
|
||||||
Some(item)
|
include_type_generics: true,
|
||||||
}
|
generic_scalar: true,
|
||||||
else {
|
no_async: attrs.no_async.is_some(),
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl#impl_generics #juniper_path::ToInputValue<#scalar> for #ident #ty_generics
|
|
||||||
#where_clause
|
|
||||||
{
|
|
||||||
fn to_input_value(&self) -> #juniper_path::InputValue<#scalar> {
|
|
||||||
#juniper_path::InputValue::object(vec![
|
|
||||||
#to_inputs
|
|
||||||
].into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
body
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
Ok(definition.into_input_object_tokens(juniper_crate_name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,75 @@
|
||||||
|
use crate::{
|
||||||
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self, Data, Fields};
|
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
use crate::util;
|
pub fn build_derive_object(
|
||||||
|
ast: syn::DeriveInput,
|
||||||
pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
let ast_span = ast.span();
|
||||||
let struct_fields = match ast.data {
|
let struct_fields = match ast.data {
|
||||||
Data::Struct(data) => match data.fields {
|
Data::Struct(data) => match data.fields {
|
||||||
Fields::Named(fields) => fields.named,
|
Fields::Named(fields) => fields.named,
|
||||||
_ => {
|
_ => return Err(error.custom_error(ast_span, "only named fields are allowed")),
|
||||||
panic!("#[derive(GraphQLObject)] may only be used on regular structs with fields");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => return Err(error.custom_error(ast_span, "can only be applied to structs")),
|
||||||
panic!("#[derive(GraphlQLObject)] may only be applied to structs, not to enums");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse attributes.
|
// Parse attributes.
|
||||||
let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) {
|
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !attrs.interfaces.is_empty() {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLObject) does not support 'interfaces'");
|
|
||||||
}
|
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
let name = attrs
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| ident.unraw().to_string());
|
||||||
|
|
||||||
let fields = struct_fields.into_iter().filter_map(|field| {
|
let fields = struct_fields
|
||||||
let field_attrs = match util::FieldAttributes::from_attrs(
|
.into_iter()
|
||||||
field.attrs,
|
.filter_map(|field| {
|
||||||
util::FieldAttributeParseMode::Object,
|
let span = field.span();
|
||||||
) {
|
let field_attrs = match util::FieldAttributes::from_attrs(
|
||||||
Ok(attrs) => attrs,
|
&field.attrs,
|
||||||
Err(e) => panic!("Invalid #[graphql] attribute: \n{}", e),
|
util::FieldAttributeParseMode::Object,
|
||||||
};
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(e) => {
|
||||||
|
proc_macro_error::emit_error!(e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if field_attrs.skip {
|
if field_attrs.skip.is_some() {
|
||||||
None
|
return None;
|
||||||
} else {
|
}
|
||||||
let field_name = field.ident.unwrap();
|
|
||||||
|
let field_name = &field.ident.unwrap();
|
||||||
let name = field_attrs
|
let name = field_attrs
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| util::to_camel_case(&field_name.to_string()));
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string()));
|
||||||
|
|
||||||
|
if name.starts_with("__") {
|
||||||
|
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
field_name.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = field_attrs.default {
|
||||||
|
error.unsupported_attribute_within(
|
||||||
|
default.span_ident(),
|
||||||
|
UnsupportedAttribute::Default,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let resolver_code = quote!(
|
let resolver_code = quote!(
|
||||||
&self . #field_name
|
&self . #field_name
|
||||||
|
@ -56,29 +79,61 @@ pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStr
|
||||||
name,
|
name,
|
||||||
_type: field.ty,
|
_type: field.ty,
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
description: field_attrs.description,
|
description: field_attrs.description.map(SpanContainer::into_inner),
|
||||||
deprecation: field_attrs.deprecation,
|
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
|
||||||
resolver_code,
|
resolver_code,
|
||||||
|
default: None,
|
||||||
is_type_inferred: true,
|
is_type_inferred: true,
|
||||||
is_async: false,
|
is_async: false,
|
||||||
|
span,
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
});
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Early abort after checking all fields
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
|
if !attrs.interfaces.is_empty() {
|
||||||
|
attrs.interfaces.iter().for_each(|elm| {
|
||||||
|
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(duplicates) =
|
||||||
|
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
|
||||||
|
{
|
||||||
|
error.duplicate(duplicates.iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") && !is_internal {
|
||||||
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields.is_empty() {
|
||||||
|
error.not_empty(ast_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early abort after GraphQL properties
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
let definition = util::GraphQLTypeDefiniton {
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
name,
|
name,
|
||||||
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
||||||
context: attrs.context,
|
context: attrs.context.map(SpanContainer::into_inner),
|
||||||
scalar: attrs.scalar,
|
scalar: attrs.scalar.map(SpanContainer::into_inner),
|
||||||
description: attrs.description,
|
description: attrs.description.map(SpanContainer::into_inner),
|
||||||
fields: fields.collect(),
|
fields,
|
||||||
generics: ast.generics,
|
generics: ast.generics,
|
||||||
interfaces: None,
|
interfaces: None,
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async,
|
no_async: attrs.no_async.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
definition.into_tokens(juniper_crate_name)
|
Ok(definition.into_tokens(juniper_crate_name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use crate::{
|
||||||
|
result::GraphQLScope,
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self, Data, Fields, Ident, Variant};
|
use syn::{self, spanned::Spanned, Data, Fields, Ident, Variant};
|
||||||
|
|
||||||
use crate::util;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct TransparentAttributes {
|
struct TransparentAttributes {
|
||||||
|
@ -36,9 +37,7 @@ impl syn::parse::Parse for TransparentAttributes {
|
||||||
"transparent" => {
|
"transparent" => {
|
||||||
output.transparent = Some(true);
|
output.transparent = Some(true);
|
||||||
}
|
}
|
||||||
other => {
|
_ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
|
||||||
return Err(input.error(format!("Unknown attribute: {}", other)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if input.lookahead1().peek(syn::Token![,]) {
|
if input.lookahead1().peek(syn::Token![,]) {
|
||||||
input.parse::<syn::Token![,]>()?;
|
input.parse::<syn::Token![,]>()?;
|
||||||
|
@ -55,7 +54,8 @@ impl TransparentAttributes {
|
||||||
Some(attr) => {
|
Some(attr) => {
|
||||||
let mut parsed: TransparentAttributes = attr.parse_args()?;
|
let mut parsed: TransparentAttributes = attr.parse_args()?;
|
||||||
if parsed.description.is_none() {
|
if parsed.description.is_none() {
|
||||||
parsed.description = util::get_doc_comment(attrs);
|
parsed.description =
|
||||||
|
util::get_doc_comment(attrs).map(SpanContainer::into_inner);
|
||||||
}
|
}
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
@ -64,15 +64,17 @@ impl TransparentAttributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn impl_scalar_value(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream {
|
pub fn impl_scalar_value(
|
||||||
|
ast: &syn::DeriveInput,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
|
|
||||||
match ast.data {
|
match ast.data {
|
||||||
Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, is_internal),
|
Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, is_internal, error),
|
||||||
Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, is_internal),
|
Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, is_internal, error),
|
||||||
Data::Union(_) => {
|
Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")),
|
||||||
panic!("#[derive(GraphQLScalarValue)] may not be applied to unions");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,22 +82,21 @@ fn impl_scalar_struct(
|
||||||
ast: &syn::DeriveInput,
|
ast: &syn::DeriveInput,
|
||||||
data: &syn::DataStruct,
|
data: &syn::DataStruct,
|
||||||
is_internal: bool,
|
is_internal: bool,
|
||||||
) -> TokenStream {
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
let field = match data.fields {
|
let field = match data.fields {
|
||||||
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||||
fields.unnamed.first().unwrap()
|
fields.unnamed.first().unwrap()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("#[derive(GraphQLScalarValue)] may only be applied to enums or tuple structs with a single field");
|
return Err(error.custom_error(
|
||||||
|
data.fields.span(),
|
||||||
|
"requires exact one field, e.g., Test(i32)",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let attrs = match TransparentAttributes::from_attrs(&ast.attrs) {
|
let attrs = TransparentAttributes::from_attrs(&ast.attrs)?;
|
||||||
Ok(attrs) => attrs,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid #[graphql] attribute: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let inner_ty = &field.ty;
|
let inner_ty = &field.ty;
|
||||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ fn impl_scalar_struct(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
quote!(
|
let content = quote!(
|
||||||
#_async
|
#_async
|
||||||
|
|
||||||
impl<S> #crate_name::GraphQLType<S> for #ident
|
impl<S> #crate_name::GraphQLType<S> for #ident
|
||||||
|
@ -198,27 +199,33 @@ fn impl_scalar_struct(
|
||||||
<#inner_ty as #crate_name::ParseScalarValue<S>>::from_str(value)
|
<#inner_ty as #crate_name::ParseScalarValue<S>>::from_str(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_scalar_enum(ident: &syn::Ident, data: &syn::DataEnum, is_internal: bool) -> TokenStream {
|
fn impl_scalar_enum(
|
||||||
|
ident: &syn::Ident,
|
||||||
|
data: &syn::DataEnum,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
let froms = data
|
let froms = data
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| derive_from_variant(v, ident))
|
.map(|v| derive_from_variant(v, ident, &error))
|
||||||
.collect::<Result<Vec<_>, String>>()
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
.unwrap_or_else(|s| panic!("{}", s));
|
|
||||||
|
|
||||||
let serialize = derive_serialize(data.variants.iter(), ident, is_internal);
|
let serialize = derive_serialize(data.variants.iter(), ident, is_internal);
|
||||||
|
|
||||||
let display = derive_display(data.variants.iter(), ident);
|
let display = derive_display(data.variants.iter(), ident);
|
||||||
|
|
||||||
quote! {
|
Ok(quote! {
|
||||||
#(#froms)*
|
#(#froms)*
|
||||||
|
|
||||||
#serialize
|
#serialize
|
||||||
#display
|
#display
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
|
fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
|
||||||
|
@ -269,14 +276,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_from_variant(variant: &Variant, ident: &Ident) -> Result<TokenStream, String> {
|
fn derive_from_variant(
|
||||||
|
variant: &Variant,
|
||||||
|
ident: &Ident,
|
||||||
|
error: &GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
let ty = match variant.fields {
|
let ty = match variant.fields {
|
||||||
Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,
|
Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(String::from(
|
return Err(error.custom_error(
|
||||||
"Only enums with exactly one unnamed field per variant are supported",
|
variant.fields.span(),
|
||||||
));
|
"requires exact one field, e.g., Test(i32)",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,58 @@
|
||||||
|
use crate::{
|
||||||
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self, Data, Fields};
|
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
use crate::util;
|
pub fn build_derive_union(
|
||||||
|
ast: syn::DeriveInput,
|
||||||
pub fn build_derive_union(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
let ast_span = ast.span();
|
||||||
let enum_fields = match ast.data {
|
let enum_fields = match ast.data {
|
||||||
Data::Enum(data) => data.variants,
|
Data::Enum(data) => data.variants,
|
||||||
_ => {
|
_ => return Err(error.custom_error(ast_span, "can only be applied to enums")),
|
||||||
panic!("#[derive(GraphQLUnion)] can only be applied to enums");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse attributes.
|
// Parse attributes.
|
||||||
let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) {
|
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid #[graphql(...)] attribute for enum: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !attrs.interfaces.is_empty() {
|
|
||||||
panic!("#[derive(GraphQLUnion)] does not support interfaces");
|
|
||||||
}
|
|
||||||
|
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
let name = attrs
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| ident.unraw().to_string());
|
||||||
|
|
||||||
let fields = enum_fields.into_iter().filter_map(|field| {
|
let fields = enum_fields
|
||||||
let field_attrs = match util::FieldAttributes::from_attrs(
|
.into_iter()
|
||||||
field.attrs,
|
.filter_map(|field| {
|
||||||
util::FieldAttributeParseMode::Object,
|
let span = field.span();
|
||||||
) {
|
let field_attrs = match util::FieldAttributes::from_attrs(
|
||||||
Ok(attrs) => attrs,
|
&field.attrs,
|
||||||
Err(e) => panic!("Invalid #[graphql] attribute for field: \n{}", e),
|
util::FieldAttributeParseMode::Object,
|
||||||
};
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(e) => {
|
||||||
|
proc_macro_error::emit_error!(e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ident) = field_attrs.skip {
|
||||||
|
error.unsupported_attribute_within(ident.span(), UnsupportedAttribute::Skip);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if field_attrs.skip {
|
|
||||||
panic!("#[derive(GraphQLUnion)] does not support #[graphql(skip)] on fields");
|
|
||||||
} else {
|
|
||||||
let variant_name = field.ident;
|
let variant_name = field.ident;
|
||||||
let name = field_attrs
|
let name = field_attrs
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| util::to_camel_case(&variant_name.to_string()));
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&variant_name.unraw().to_string()));
|
||||||
|
|
||||||
let resolver_code = quote!(
|
let resolver_code = quote!(
|
||||||
#ident :: #variant_name
|
#ident :: #variant_name
|
||||||
|
@ -59,16 +67,44 @@ pub fn build_derive_union(ast: syn::DeriveInput, is_internal: bool) -> TokenStre
|
||||||
};
|
};
|
||||||
|
|
||||||
if iter.next().is_some() {
|
if iter.next().is_some() {
|
||||||
panic!("#[derive(GraphQLUnion)] all members must be unnamed with a single element e.g. Some(T)");
|
error.custom(
|
||||||
|
inner.span(),
|
||||||
|
"all members must be unnamed with a single element e.g. Some(T)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
first.ty.clone()
|
first.ty.clone()
|
||||||
}
|
}
|
||||||
_ => panic!("#[derive(GraphQLUnion)] all fields of the enum must be unnamed"),
|
_ => {
|
||||||
|
error.custom(
|
||||||
|
variant_name.span(),
|
||||||
|
"only unnamed fields with a single element are allowed, e.g., Some(T)",
|
||||||
|
);
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if field_attrs.description.is_some() {
|
if let Some(description) = field_attrs.description {
|
||||||
panic!("#[derive(GraphQLUnion)] does not allow documentation of fields");
|
error.unsupported_attribute_within(
|
||||||
|
description.span_ident(),
|
||||||
|
UnsupportedAttribute::Description,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = field_attrs.default {
|
||||||
|
error.unsupported_attribute_within(
|
||||||
|
default.span_ident(),
|
||||||
|
UnsupportedAttribute::Default,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") {
|
||||||
|
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
variant_name.span()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(util::GraphQLTypeDefinitionField {
|
Some(util::GraphQLTypeDefinitionField {
|
||||||
|
@ -76,15 +112,36 @@ pub fn build_derive_union(ast: syn::DeriveInput, is_internal: bool) -> TokenStre
|
||||||
_type,
|
_type,
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
description: None,
|
description: None,
|
||||||
deprecation: field_attrs.deprecation,
|
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
|
||||||
resolver_code,
|
resolver_code,
|
||||||
is_type_inferred: true,
|
is_type_inferred: true,
|
||||||
is_async: false,
|
is_async: false,
|
||||||
|
default: None,
|
||||||
|
span,
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
});
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let fields = fields.collect::<Vec<_>>();
|
// Early abort after checking all fields
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
|
if !attrs.interfaces.is_empty() {
|
||||||
|
attrs.interfaces.iter().for_each(|elm| {
|
||||||
|
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields.is_empty() {
|
||||||
|
error.not_empty(ast_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") && !is_internal {
|
||||||
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// NOTICE: This is not an optimal implementation. It is possible
|
// NOTICE: This is not an optimal implementation. It is possible
|
||||||
// to bypass this check by using a full qualified path instead
|
// to bypass this check by using a full qualified path instead
|
||||||
|
@ -100,23 +157,26 @@ pub fn build_derive_union(ast: syn::DeriveInput, is_internal: bool) -> TokenStre
|
||||||
};
|
};
|
||||||
|
|
||||||
if !all_variants_different {
|
if !all_variants_different {
|
||||||
panic!("#[derive(GraphQLUnion)] each variant must have a different type");
|
error.custom(ident.span(), "each variant must have a different type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Early abort after GraphQL properties
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
let definition = util::GraphQLTypeDefiniton {
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
name,
|
name,
|
||||||
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
||||||
context: attrs.context,
|
context: attrs.context.map(SpanContainer::into_inner),
|
||||||
scalar: attrs.scalar,
|
scalar: attrs.scalar.map(SpanContainer::into_inner),
|
||||||
description: attrs.description,
|
description: attrs.description.map(SpanContainer::into_inner),
|
||||||
fields,
|
fields,
|
||||||
generics: ast.generics,
|
generics: ast.generics,
|
||||||
interfaces: None,
|
interfaces: None,
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async,
|
no_async: attrs.no_async.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
definition.into_union_tokens(juniper_crate_name)
|
Ok(definition.into_union_tokens(juniper_crate_name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,181 +1,234 @@
|
||||||
#![allow(clippy::collapsible_if)]
|
#![allow(clippy::collapsible_if)]
|
||||||
|
|
||||||
use crate::util;
|
use crate::{
|
||||||
use proc_macro::TokenStream;
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::{ext::IdentExt, spanned::Spanned};
|
||||||
|
|
||||||
/// Generate code for the juniper::graphql_object macro.
|
/// Generate code for the juniper::graphql_object macro.
|
||||||
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
pub fn build_object(
|
||||||
let definition = create(args, body);
|
args: TokenStream,
|
||||||
|
body: TokenStream,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> TokenStream {
|
||||||
|
let definition = match create(args, body, is_internal, error) {
|
||||||
|
Ok(definition) => definition,
|
||||||
|
Err(err) => return err.to_compile_error(),
|
||||||
|
};
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
|
||||||
definition.into_tokens(juniper_crate_name).into()
|
definition.into_tokens(juniper_crate_name).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate code for the juniper::graphql_subscription macro.
|
/// Generate code for the juniper::graphql_subscription macro.
|
||||||
pub fn build_subscription(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
pub fn build_subscription(
|
||||||
let definition = create(args, body);
|
args: TokenStream,
|
||||||
|
body: TokenStream,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> TokenStream {
|
||||||
|
let definition = match create(args, body, is_internal, error) {
|
||||||
|
Ok(definition) => definition,
|
||||||
|
Err(err) => return err.to_compile_error(),
|
||||||
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
definition
|
definition
|
||||||
.into_subscription_tokens(juniper_crate_name)
|
.into_subscription_tokens(juniper_crate_name)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(args: TokenStream, body: TokenStream) -> util::GraphQLTypeDefiniton {
|
fn create(
|
||||||
let _impl = util::parse_impl::ImplBlock::parse(args, body);
|
args: TokenStream,
|
||||||
|
body: TokenStream,
|
||||||
|
is_internal: bool,
|
||||||
|
error: GraphQLScope,
|
||||||
|
) -> syn::Result<util::GraphQLTypeDefiniton> {
|
||||||
|
let body_span = body.span();
|
||||||
|
let _impl = util::parse_impl::ImplBlock::parse(args, body)?;
|
||||||
let name = _impl
|
let name = _impl
|
||||||
.attrs
|
.attrs
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| _impl.type_ident.to_string());
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| _impl.type_ident.unraw().to_string());
|
||||||
|
|
||||||
let mut definition = util::GraphQLTypeDefiniton {
|
let fields = _impl
|
||||||
|
.methods
|
||||||
|
.iter()
|
||||||
|
.filter_map(|method| {
|
||||||
|
let span = method.span();
|
||||||
|
let _type = match method.sig.output {
|
||||||
|
syn::ReturnType::Type(_, ref t) => *t.clone(),
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
error.custom(method.sig.span(), "return value required");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_async = method.sig.asyncness.is_some();
|
||||||
|
|
||||||
|
let attrs = match util::FieldAttributes::from_attrs(
|
||||||
|
&method.attrs,
|
||||||
|
util::FieldAttributeParseMode::Impl,
|
||||||
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(err) => {
|
||||||
|
proc_macro_error::emit_error!(err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let parse_method =
|
||||||
|
_impl.parse_method(&method, true, |captured, arg_ident, is_mut: bool| {
|
||||||
|
let arg_name = arg_ident.unraw().to_string();
|
||||||
|
let ty = &captured.ty;
|
||||||
|
|
||||||
|
let final_name = attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|attrs| attrs.rename.clone().map(|ident| ident.value()))
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&arg_name));
|
||||||
|
|
||||||
|
let expect_text = format!(
|
||||||
|
"Internal error: missing argument {} - validation must have failed",
|
||||||
|
&final_name
|
||||||
|
);
|
||||||
|
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
||||||
|
|
||||||
|
if final_name.starts_with("__") {
|
||||||
|
error.no_double_underscore(
|
||||||
|
if let Some(name) = attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|attrs| attrs.rename.as_ref())
|
||||||
|
{
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
arg_ident.span()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolver = quote!(
|
||||||
|
let #mut_modifier #arg_ident = args
|
||||||
|
.get::<#ty>(#final_name)
|
||||||
|
.expect(#expect_text);
|
||||||
|
);
|
||||||
|
|
||||||
|
let field_type = util::GraphQLTypeDefinitionFieldArg {
|
||||||
|
description: attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|arg| arg.description.as_ref().map(|d| d.value())),
|
||||||
|
default: attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|arg| arg.default.clone()),
|
||||||
|
_type: ty.clone(),
|
||||||
|
name: final_name,
|
||||||
|
};
|
||||||
|
Ok((resolver, field_type))
|
||||||
|
});
|
||||||
|
|
||||||
|
let (resolve_parts, args) = match parse_method {
|
||||||
|
Ok((resolve_parts, args)) => (resolve_parts, args),
|
||||||
|
Err(err) => {
|
||||||
|
proc_macro_error::emit_error!(err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = &method.block;
|
||||||
|
let resolver_code = quote!(
|
||||||
|
#( #resolve_parts )*
|
||||||
|
#body
|
||||||
|
);
|
||||||
|
|
||||||
|
let ident = &method.sig.ident;
|
||||||
|
let name = attrs
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&ident.unraw().to_string()));
|
||||||
|
|
||||||
|
if name.starts_with("__") {
|
||||||
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = attrs.default {
|
||||||
|
error.unsupported_attribute_within(
|
||||||
|
default.span_ident(),
|
||||||
|
UnsupportedAttribute::Default,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(util::GraphQLTypeDefinitionField {
|
||||||
|
name,
|
||||||
|
_type,
|
||||||
|
args,
|
||||||
|
description: attrs.description.map(SpanContainer::into_inner),
|
||||||
|
deprecation: attrs.deprecation.map(SpanContainer::into_inner),
|
||||||
|
resolver_code,
|
||||||
|
is_type_inferred: false,
|
||||||
|
is_async,
|
||||||
|
default: None,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Early abort after checking all fields
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
|
match crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) {
|
||||||
|
Some(duplicates) => error.duplicate(duplicates.iter()),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with("__") && !is_internal {
|
||||||
|
error.no_double_underscore(if let Some(name) = _impl.attrs.name {
|
||||||
|
name.span_ident()
|
||||||
|
} else {
|
||||||
|
_impl.type_ident.span()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields.is_empty() {
|
||||||
|
error.not_empty(body_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early abort after GraphQL properties
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
name,
|
name,
|
||||||
_type: *_impl.target_type.clone(),
|
_type: *_impl.target_type.clone(),
|
||||||
context: _impl.attrs.context,
|
scalar: _impl.attrs.scalar.map(SpanContainer::into_inner),
|
||||||
scalar: _impl.attrs.scalar,
|
context: _impl.attrs.context.map(SpanContainer::into_inner),
|
||||||
description: _impl.description,
|
description: _impl.description,
|
||||||
fields: Vec::new(),
|
fields,
|
||||||
generics: _impl.generics.clone(),
|
generics: _impl.generics.clone(),
|
||||||
interfaces: if !_impl.attrs.interfaces.is_empty() {
|
interfaces: if !_impl.attrs.interfaces.is_empty() {
|
||||||
Some(_impl.attrs.interfaces)
|
Some(
|
||||||
|
_impl
|
||||||
|
.attrs
|
||||||
|
.interfaces
|
||||||
|
.into_iter()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
include_type_generics: false,
|
include_type_generics: false,
|
||||||
generic_scalar: false,
|
generic_scalar: false,
|
||||||
no_async: _impl.attrs.no_async,
|
no_async: _impl.attrs.no_async.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for method in _impl.methods {
|
Ok(definition)
|
||||||
let _type = match &method.sig.output {
|
|
||||||
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
|
||||||
syn::ReturnType::Default => {
|
|
||||||
panic!(
|
|
||||||
"Invalid field method {}: must return a value",
|
|
||||||
method.sig.ident
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_async = method.sig.asyncness.is_some();
|
|
||||||
|
|
||||||
let attrs = match util::FieldAttributes::from_attrs(
|
|
||||||
method.attrs,
|
|
||||||
util::FieldAttributeParseMode::Impl,
|
|
||||||
) {
|
|
||||||
Ok(attrs) => attrs,
|
|
||||||
Err(err) => panic!(
|
|
||||||
"Invalid #[graphql(...)] attribute on field {}:\n{}",
|
|
||||||
method.sig.ident, err
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
|
||||||
let mut resolve_parts = Vec::new();
|
|
||||||
|
|
||||||
for arg in method.sig.inputs {
|
|
||||||
match arg {
|
|
||||||
syn::FnArg::Receiver(rec) => {
|
|
||||||
if rec.reference.is_none() || rec.mutability.is_some() {
|
|
||||||
panic!(
|
|
||||||
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
|
||||||
method.sig.ident
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syn::FnArg::Typed(ref captured) => {
|
|
||||||
let (arg_ident, is_mut) = match &*captured.pat {
|
|
||||||
syn::Pat::Ident(ref pat_ident) => {
|
|
||||||
(&pat_ident.ident, pat_ident.mutability.is_some())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Invalid token for function argument");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let arg_name = arg_ident.to_string();
|
|
||||||
|
|
||||||
let context_type = definition.context.as_ref();
|
|
||||||
|
|
||||||
// Check for executor arguments.
|
|
||||||
if util::type_is_identifier_ref(&captured.ty, "Executor") {
|
|
||||||
resolve_parts.push(quote!(let #arg_ident = executor;));
|
|
||||||
}
|
|
||||||
// Make sure executor is specified as a reference.
|
|
||||||
else if util::type_is_identifier(&captured.ty, "Executor") {
|
|
||||||
panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?");
|
|
||||||
}
|
|
||||||
// Check for context arg.
|
|
||||||
else if context_type
|
|
||||||
.clone()
|
|
||||||
.map(|ctx| util::type_is_ref_of(&captured.ty, ctx))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
resolve_parts.push(quote!( let #arg_ident = executor.context(); ));
|
|
||||||
}
|
|
||||||
// Make sure the user does not specify the Context
|
|
||||||
// without a reference. (&Context)
|
|
||||||
else if context_type
|
|
||||||
.clone()
|
|
||||||
.map(|ctx| ctx == &*captured.ty)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
|
||||||
quote!(captured.ty),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Regular argument.
|
|
||||||
|
|
||||||
let ty = &captured.ty;
|
|
||||||
// TODO: respect graphql attribute overwrite.
|
|
||||||
let final_name = util::to_camel_case(&arg_name);
|
|
||||||
let expect_text = format!(
|
|
||||||
"Internal error: missing argument {} - validation must have failed",
|
|
||||||
&final_name
|
|
||||||
);
|
|
||||||
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
|
||||||
resolve_parts.push(quote!(
|
|
||||||
let #mut_modifier #arg_ident = args
|
|
||||||
.get::<#ty>(#final_name)
|
|
||||||
.expect(#expect_text);
|
|
||||||
));
|
|
||||||
args.push(util::GraphQLTypeDefinitionFieldArg {
|
|
||||||
description: attrs
|
|
||||||
.argument(&arg_name)
|
|
||||||
.and_then(|arg| arg.description.as_ref().map(|d| d.value())),
|
|
||||||
default: attrs
|
|
||||||
.argument(&arg_name)
|
|
||||||
.and_then(|arg| arg.default.clone()),
|
|
||||||
_type: ty.clone(),
|
|
||||||
name: final_name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = &method.block;
|
|
||||||
let resolver_code = quote!(
|
|
||||||
#( #resolve_parts )*
|
|
||||||
#body
|
|
||||||
);
|
|
||||||
|
|
||||||
let ident = &method.sig.ident;
|
|
||||||
let name = attrs
|
|
||||||
.name
|
|
||||||
.unwrap_or_else(|| util::to_camel_case(&ident.to_string()));
|
|
||||||
|
|
||||||
definition.fields.push(util::GraphQLTypeDefinitionField {
|
|
||||||
name,
|
|
||||||
_type,
|
|
||||||
args,
|
|
||||||
description: attrs.description,
|
|
||||||
deprecation: attrs.deprecation,
|
|
||||||
resolver_code,
|
|
||||||
is_type_inferred: false,
|
|
||||||
is_async,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
definition
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
#![allow(clippy::collapsible_if)]
|
#![allow(clippy::collapsible_if)]
|
||||||
|
|
||||||
use crate::util;
|
use crate::{
|
||||||
use proc_macro::TokenStream;
|
result::GraphQLScope,
|
||||||
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ScalarCodegenInput {
|
struct ScalarCodegenInput {
|
||||||
|
@ -169,46 +173,52 @@ impl syn::parse::Parse for ScalarCodegenInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate code for the juniper::graphql_scalar proc macro.
|
/// Generate code for the juniper::graphql_scalar proc macro.
|
||||||
pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
pub fn build_scalar(
|
||||||
let attrs = match syn::parse::<util::FieldAttributes>(attributes) {
|
attributes: TokenStream,
|
||||||
Ok(attrs) => attrs,
|
body: TokenStream,
|
||||||
Err(e) => {
|
is_internal: bool,
|
||||||
panic!("Invalid attributes:\n{}", e);
|
error: GraphQLScope,
|
||||||
}
|
) -> syn::Result<TokenStream> {
|
||||||
};
|
let body_span = body.span();
|
||||||
|
|
||||||
let input = syn::parse_macro_input!(body as ScalarCodegenInput);
|
let attrs = syn::parse2::<util::FieldAttributes>(attributes)?;
|
||||||
|
let input = syn::parse2::<ScalarCodegenInput>(body)?;
|
||||||
|
|
||||||
let impl_for_type = input
|
let impl_for_type = input.impl_for_type.ok_or(error.custom_error(
|
||||||
.impl_for_type
|
body_span,
|
||||||
.expect("Unable to find target for implementation target for `GraphQLScalar`");
|
"unable to find target for implementation target for `GraphQLScalar`",
|
||||||
|
))?;
|
||||||
let custom_data_type = input
|
let custom_data_type = input
|
||||||
.custom_data_type
|
.custom_data_type
|
||||||
.expect("Unable to find custom scalar data type");
|
.ok_or(error.custom_error(body_span, "unable to find custom scalar data type"))?;
|
||||||
let resolve_body = input
|
let resolve_body = input
|
||||||
.resolve_body
|
.resolve_body
|
||||||
.expect("Unable to find body of `resolve` method");
|
.ok_or(error.custom_error(body_span, "unable to find body of `resolve` method"))?;
|
||||||
let from_input_value_arg = input
|
let from_input_value_arg = input.from_input_value_arg.ok_or(error.custom_error(
|
||||||
.from_input_value_arg
|
body_span,
|
||||||
.expect("Unable to find argument for `from_input_value` method");
|
"unable to find argument for `from_input_value` method",
|
||||||
let from_input_value_body = input
|
))?;
|
||||||
.from_input_value_body
|
let from_input_value_body = input.from_input_value_body.ok_or(error.custom_error(
|
||||||
.expect("Unable to find body of `from_input_value` method");
|
body_span,
|
||||||
let from_input_value_result = input
|
"unable to find body of `from_input_value` method",
|
||||||
.from_input_value_result
|
))?;
|
||||||
.expect("Unable to find return type of `from_input_value` method");
|
let from_input_value_result = input.from_input_value_result.ok_or(error.custom_error(
|
||||||
|
body_span,
|
||||||
|
"unable to find return type of `from_input_value` method",
|
||||||
|
))?;
|
||||||
let from_str_arg = input
|
let from_str_arg = input
|
||||||
.from_str_arg
|
.from_str_arg
|
||||||
.expect("Unable to find argument for `from_str` method");
|
.ok_or(error.custom_error(body_span, "unable to find argument for `from_str` method"))?;
|
||||||
let from_str_body = input
|
let from_str_body = input
|
||||||
.from_str_body
|
.from_str_body
|
||||||
.expect("Unable to find body of `from_str` method");
|
.ok_or(error.custom_error(body_span, "unable to find body of `from_str` method"))?;
|
||||||
let from_str_result = input
|
let from_str_result = input
|
||||||
.from_str_result
|
.from_str_result
|
||||||
.expect("Unable to find return type of `from_str` method");
|
.ok_or(error.custom_error(body_span, "unable to find return type of `from_str` method"))?;
|
||||||
|
|
||||||
let name = attrs
|
let name = attrs
|
||||||
.name
|
.name
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| impl_for_type.ident.to_string());
|
.unwrap_or_else(|| impl_for_type.ident.to_string());
|
||||||
let crate_name = match is_internal {
|
let crate_name = match is_internal {
|
||||||
true => quote!(crate),
|
true => quote!(crate),
|
||||||
|
@ -261,9 +271,15 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
quote!(
|
let content = quote!(
|
||||||
#_async
|
#_async
|
||||||
|
|
||||||
|
impl#generic_type_decl #crate_name::marker::IsInputType<#generic_type> for #impl_for_type
|
||||||
|
#generic_type_bound { }
|
||||||
|
|
||||||
|
impl#generic_type_decl #crate_name::marker::IsOutputType<#generic_type> for #impl_for_type
|
||||||
|
#generic_type_bound { }
|
||||||
|
|
||||||
impl#generic_type_decl #crate_name::GraphQLType<#generic_type> for #impl_for_type
|
impl#generic_type_decl #crate_name::GraphQLType<#generic_type> for #impl_for_type
|
||||||
#generic_type_bound
|
#generic_type_bound
|
||||||
{
|
{
|
||||||
|
@ -322,5 +338,7 @@ pub fn build_scalar(attributes: TokenStream, body: TokenStream, is_internal: boo
|
||||||
#from_str_body
|
#from_str_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).into()
|
);
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use proc_macro::TokenStream;
|
use crate::{
|
||||||
|
result::GraphQLScope,
|
||||||
use proc_macro_error::MacroError;
|
util::{self, span_container::SpanContainer},
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::spanned::Spanned;
|
use syn::{ext::IdentExt, spanned::Spanned};
|
||||||
|
|
||||||
use crate::util;
|
|
||||||
|
|
||||||
struct ResolverVariant {
|
struct ResolverVariant {
|
||||||
pub ty: syn::Type,
|
pub ty: syn::Type,
|
||||||
|
@ -36,7 +36,7 @@ impl syn::parse::Parse for ResolveBody {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
return Err(input.error("Unexpected input"));
|
return Err(input.error("unexpected input"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { variants })
|
Ok(Self { variants })
|
||||||
|
@ -47,15 +47,18 @@ pub fn impl_union(
|
||||||
is_internal: bool,
|
is_internal: bool,
|
||||||
attrs: TokenStream,
|
attrs: TokenStream,
|
||||||
body: TokenStream,
|
body: TokenStream,
|
||||||
) -> Result<TokenStream, MacroError> {
|
error: GraphQLScope,
|
||||||
let _impl = util::parse_impl::ImplBlock::parse(attrs, body);
|
) -> syn::Result<TokenStream> {
|
||||||
|
let body_span = body.span();
|
||||||
|
let _impl = util::parse_impl::ImplBlock::parse(attrs, body)?;
|
||||||
|
|
||||||
|
// FIXME: what is the purpose of this construct?
|
||||||
// Validate trait target name, if present.
|
// Validate trait target name, if present.
|
||||||
if let Some((name, path)) = &_impl.target_trait {
|
if let Some((name, path)) = &_impl.target_trait {
|
||||||
if !(name == "GraphQLUnion" || name == "juniper.GraphQLUnion") {
|
if !(name == "GraphQLUnion" || name == "juniper.GraphQLUnion") {
|
||||||
return Err(MacroError::new(
|
return Err(error.custom_error(
|
||||||
path.span(),
|
path.span(),
|
||||||
"Invalid impl target trait: expected 'GraphQLUnion'".to_string(),
|
"Invalid impl target trait: expected 'GraphQLUnion'",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,8 @@ pub fn impl_union(
|
||||||
.attrs
|
.attrs
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| type_ident.to_string());
|
.map(SpanContainer::into_inner)
|
||||||
|
.unwrap_or_else(|| type_ident.unraw().to_string());
|
||||||
let crate_name = util::juniper_path(is_internal);
|
let crate_name = util::juniper_path(is_internal);
|
||||||
|
|
||||||
let scalar = _impl
|
let scalar = _impl
|
||||||
|
@ -77,36 +81,33 @@ pub fn impl_union(
|
||||||
quote! { #crate_name::DefaultScalarValue }
|
quote! { #crate_name::DefaultScalarValue }
|
||||||
});
|
});
|
||||||
|
|
||||||
if !_impl.has_resolve_method() {
|
|
||||||
return Err(MacroError::new(
|
|
||||||
_impl.target_type.span(),
|
|
||||||
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
|
||||||
.to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = _impl
|
let method = _impl
|
||||||
.methods
|
.methods
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&m| _impl.parse_resolve_method(&m).is_ok());
|
.find(|&m| _impl.parse_resolve_method(&m).is_ok());
|
||||||
|
|
||||||
if _impl.methods.is_empty() || method.is_none() {
|
let method = match method {
|
||||||
return Err(MacroError::new(
|
Some(method) => method,
|
||||||
_impl.target_type.span(),
|
None => {
|
||||||
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
return Err(error.custom_error(
|
||||||
.to_string(),
|
body_span,
|
||||||
));
|
"expected exactly one method with signature: fn resolve(&self) { ... }",
|
||||||
}
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let method = method.expect("checked above");
|
let resolve_args = _impl.parse_resolve_method(method)?;
|
||||||
let resolve_args = _impl
|
|
||||||
.parse_resolve_method(method)
|
|
||||||
.expect("Invalid impl body: expected one method with signature: fn resolve(&self) { ... }");
|
|
||||||
|
|
||||||
let stmts = &method.block.stmts;
|
let stmts = &method.block.stmts;
|
||||||
let body_raw = quote!( #( #stmts )* );
|
let body_raw = quote!( #( #stmts )* );
|
||||||
let body = syn::parse::<ResolveBody>(body_raw.into())?;
|
let body = syn::parse::<ResolveBody>(body_raw.into())?;
|
||||||
|
|
||||||
|
if body.variants.is_empty() {
|
||||||
|
error.not_empty(method.span())
|
||||||
|
}
|
||||||
|
|
||||||
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
let meta_types = body.variants.iter().map(|var| {
|
let meta_types = body.variants.iter().map(|var| {
|
||||||
let var_ty = &var.ty;
|
let var_ty = &var.ty;
|
||||||
|
|
||||||
|
@ -152,7 +153,20 @@ pub fn impl_union(
|
||||||
|
|
||||||
let ty = _impl.target_type;
|
let ty = _impl.target_type;
|
||||||
|
|
||||||
|
let object_marks = body.variants.iter().map(|field| {
|
||||||
|
let _ty = &field.ty;
|
||||||
|
quote!(
|
||||||
|
<#_ty as #crate_name::marker::GraphQLObjectType<#scalar>>::mark();
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
|
impl #impl_generics #crate_name::marker::IsOutputType<#scalar> for #ty #where_clause {
|
||||||
|
fn mark() {
|
||||||
|
#( #object_marks )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl #impl_generics #crate_name::GraphQLType<#scalar> for #ty #where_clause
|
impl #impl_generics #crate_name::GraphQLType<#scalar> for #ty #where_clause
|
||||||
{
|
{
|
||||||
type Context = #context;
|
type Context = #context;
|
||||||
|
@ -204,5 +218,6 @@ pub fn impl_union(
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(output.into())
|
Ok(output.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
mod result;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
mod derive_enum;
|
mod derive_enum;
|
||||||
|
@ -21,56 +22,86 @@ mod impl_scalar;
|
||||||
mod impl_union;
|
mod impl_union;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro_error::proc_macro_error;
|
||||||
|
use result::GraphQLScope;
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLEnum, attributes(graphql))]
|
#[proc_macro_derive(GraphQLEnum, attributes(graphql))]
|
||||||
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_enum::impl_enum(ast, false);
|
let gen = derive_enum::impl_enum(ast, false, GraphQLScope::DeriveEnum);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLEnumInternal, attributes(graphql))]
|
#[proc_macro_derive(GraphQLEnumInternal, attributes(graphql))]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn derive_enum_internal(input: TokenStream) -> TokenStream {
|
pub fn derive_enum_internal(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_enum::impl_enum(ast, true);
|
let gen = derive_enum::impl_enum(ast, true, GraphQLScope::DeriveEnum);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLInputObject, attributes(graphql))]
|
#[proc_macro_derive(GraphQLInputObject, attributes(graphql))]
|
||||||
pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_input_object::impl_input_object(&ast, false);
|
let gen = derive_input_object::impl_input_object(ast, false, GraphQLScope::DeriveInputObject);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLInputObjectInternal, attributes(graphql))]
|
#[proc_macro_derive(GraphQLInputObjectInternal, attributes(graphql))]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn derive_input_object_internal(input: TokenStream) -> TokenStream {
|
pub fn derive_input_object_internal(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_input_object::impl_input_object(&ast, true);
|
let gen = derive_input_object::impl_input_object(ast, true, GraphQLScope::DeriveInputObject);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLObject, attributes(graphql))]
|
#[proc_macro_derive(GraphQLObject, attributes(graphql))]
|
||||||
pub fn derive_object(input: TokenStream) -> TokenStream {
|
pub fn derive_object(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_object::build_derive_object(ast, false);
|
let gen = derive_object::build_derive_object(ast, false, GraphQLScope::DeriveObject);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLObjectInternal, attributes(graphql))]
|
#[proc_macro_derive(GraphQLObjectInternal, attributes(graphql))]
|
||||||
pub fn derive_object_internal(input: TokenStream) -> TokenStream {
|
pub fn derive_object_internal(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_object::build_derive_object(ast, true);
|
let gen = derive_object::build_derive_object(ast, true, GraphQLScope::DeriveObject);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLUnion, attributes(graphql))]
|
#[proc_macro_derive(GraphQLUnion, attributes(graphql))]
|
||||||
pub fn derive_union(input: TokenStream) -> TokenStream {
|
pub fn derive_union(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_union::build_derive_union(ast, false);
|
let gen = derive_union::build_derive_union(ast, false, GraphQLScope::DeriveUnion);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
|
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
|
||||||
/// derive.
|
/// derive.
|
||||||
|
@ -114,19 +145,27 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// TODO: write documentation.
|
/// TODO: write documentation.
|
||||||
///
|
///
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
|
#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
|
||||||
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_scalar_value::impl_scalar_value(&ast, false);
|
let gen = derive_scalar_value::impl_scalar_value(&ast, false, GraphQLScope::DeriveScalar);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLScalarValueInternal)]
|
#[proc_macro_derive(GraphQLScalarValueInternal)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream {
|
pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_scalar_value::impl_scalar_value(&ast, true);
|
let gen = derive_scalar_value::impl_scalar_value(&ast, true, GraphQLScope::DeriveScalar);
|
||||||
gen.into()
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -359,7 +398,9 @@ struct Query;
|
||||||
Scalar = MyCustomScalar,
|
Scalar = MyCustomScalar,
|
||||||
)]
|
)]
|
||||||
impl Query {
|
impl Query {
|
||||||
// ...
|
fn test(&self) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -382,16 +423,32 @@ impl User {
|
||||||
```
|
```
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_object::build_object(args, input, false)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
TokenStream::from(impl_object::build_object(
|
||||||
|
args,
|
||||||
|
input,
|
||||||
|
false,
|
||||||
|
GraphQLScope::ImplObject,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A proc macro for defining a GraphQL object.
|
/// A proc macro for defining a GraphQL object.
|
||||||
#[doc(hidden)]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_object::build_object(args, input, true)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
TokenStream::from(impl_object::build_object(
|
||||||
|
args,
|
||||||
|
input,
|
||||||
|
true,
|
||||||
|
GraphQLScope::ImplObject,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expose GraphQL scalars
|
/// Expose GraphQL scalars
|
||||||
|
@ -443,45 +500,81 @@ pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenSt
|
||||||
/// In addition to implementing `GraphQLType` for the type in question,
|
/// In addition to implementing `GraphQLType` for the type in question,
|
||||||
/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type
|
/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type
|
||||||
/// usable as arguments and default values.
|
/// usable as arguments and default values.
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_scalar::build_scalar(args, input, false)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
let gen = impl_scalar::build_scalar(args, input, false, GraphQLScope::ImplScalar);
|
||||||
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A proc macro for defining a GraphQL scalar.
|
/// A proc macro for defining a GraphQL scalar.
|
||||||
#[doc(hidden)]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn graphql_scalar_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_scalar_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_scalar::build_scalar(args, input, true)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
let gen = impl_scalar::build_scalar(args, input, true, GraphQLScope::ImplScalar);
|
||||||
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A proc macro for defining a GraphQL subscription.
|
/// A proc macro for defining a GraphQL subscription.
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_object::build_subscription(args, input, false)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
TokenStream::from(impl_object::build_subscription(
|
||||||
|
args,
|
||||||
|
input,
|
||||||
|
false,
|
||||||
|
GraphQLScope::ImplObject,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn graphql_subscription_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_subscription_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
impl_object::build_subscription(args, input, true)
|
let args = proc_macro2::TokenStream::from(args);
|
||||||
|
let input = proc_macro2::TokenStream::from(input);
|
||||||
|
TokenStream::from(impl_object::build_subscription(
|
||||||
|
args,
|
||||||
|
input,
|
||||||
|
true,
|
||||||
|
GraphQLScope::ImplObject,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
#[proc_macro_error::proc_macro_error]
|
|
||||||
pub fn graphql_union(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
pub fn graphql_union(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
match impl_union::impl_union(false, attrs, body) {
|
let attrs = proc_macro2::TokenStream::from(attrs);
|
||||||
Ok(toks) => toks,
|
let body = proc_macro2::TokenStream::from(body);
|
||||||
|
let gen = impl_union::impl_union(false, attrs, body, GraphQLScope::ImplUnion);
|
||||||
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
Err(err) => proc_macro_error::abort!(err),
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
#[proc_macro_error::proc_macro_error]
|
#[doc(hidden)]
|
||||||
pub fn graphql_union_internal(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
pub fn graphql_union_internal(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
match impl_union::impl_union(true, attrs, body) {
|
let attrs = proc_macro2::TokenStream::from(attrs);
|
||||||
Ok(toks) => toks,
|
let body = proc_macro2::TokenStream::from(body);
|
||||||
|
let gen = impl_union::impl_union(true, attrs, body, GraphQLScope::ImplUnion);
|
||||||
|
match gen {
|
||||||
|
Ok(gen) => gen.into(),
|
||||||
Err(err) => proc_macro_error::abort!(err),
|
Err(err) => proc_macro_error::abort!(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
juniper_codegen/src/result.rs
Normal file
138
juniper_codegen/src/result.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
//!
|
||||||
|
|
||||||
|
use crate::util::duplicate::Duplicate;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use proc_macro_error::{Diagnostic, Level};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub const GRAPHQL_SPECIFICATION: &'static str = "https://spec.graphql.org/June2018/";
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub enum GraphQLScope {
|
||||||
|
DeriveObject,
|
||||||
|
DeriveInputObject,
|
||||||
|
DeriveUnion,
|
||||||
|
DeriveEnum,
|
||||||
|
DeriveScalar,
|
||||||
|
ImplUnion,
|
||||||
|
ImplScalar,
|
||||||
|
ImplObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLScope {
|
||||||
|
pub fn specification_section(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "#sec-Objects",
|
||||||
|
GraphQLScope::DeriveInputObject => "#sec-Input-Objects",
|
||||||
|
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "#sec-Unions",
|
||||||
|
GraphQLScope::DeriveEnum => "#sec-Enums",
|
||||||
|
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "#sec-Scalars",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for GraphQLScope {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let name = match self {
|
||||||
|
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "object",
|
||||||
|
GraphQLScope::DeriveInputObject => "input object",
|
||||||
|
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "union",
|
||||||
|
GraphQLScope::DeriveEnum => "enum",
|
||||||
|
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "scalar",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "GraphQL {}", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UnsupportedAttribute {
|
||||||
|
Skip,
|
||||||
|
Interface,
|
||||||
|
Scalar,
|
||||||
|
Description,
|
||||||
|
Deprecation,
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLScope {
|
||||||
|
fn specification_link(&self) -> String {
|
||||||
|
format!("{}{}", GRAPHQL_SPECIFICATION, self.specification_section())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn custom<S: AsRef<str>>(&self, span: Span, msg: S) {
|
||||||
|
Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref()))
|
||||||
|
.note(self.specification_link())
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn custom_error<S: AsRef<str>>(&self, span: Span, msg: S) -> syn::Error {
|
||||||
|
syn::Error::new(span, format!("{} {}", self, msg.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsupported_attribute(&self, attribute: Span, kind: UnsupportedAttribute) {
|
||||||
|
Diagnostic::spanned(
|
||||||
|
attribute,
|
||||||
|
Level::Error,
|
||||||
|
format!("attribute `{:?}` can not be used at the top level of {}", kind, self),
|
||||||
|
)
|
||||||
|
.note("The macro is known to Juniper. However, not all valid #[graphql] attributes are available for each macro".to_string())
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsupported_attribute_within(&self, attribute: Span, kind: UnsupportedAttribute) {
|
||||||
|
Diagnostic::spanned(
|
||||||
|
attribute,
|
||||||
|
Level::Error,
|
||||||
|
format!("attribute `{:?}` can not be used inside of {}", kind, self),
|
||||||
|
)
|
||||||
|
.note("The macro is known to Juniper. However, not all valid #[graphql] attributes are available for each macro".to_string())
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_empty(&self, container: Span) {
|
||||||
|
Diagnostic::spanned(
|
||||||
|
container,
|
||||||
|
Level::Error,
|
||||||
|
format!("{} expects at least one field", self),
|
||||||
|
)
|
||||||
|
.note(self.specification_link())
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn duplicate<'a, T: syn::spanned::Spanned + 'a>(
|
||||||
|
&self,
|
||||||
|
duplicates: impl IntoIterator<Item = &'a Duplicate<T>>,
|
||||||
|
) {
|
||||||
|
duplicates
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|dup| {
|
||||||
|
(&dup.spanned[1..])
|
||||||
|
.iter()
|
||||||
|
.for_each(|spanned| {
|
||||||
|
Diagnostic::spanned(
|
||||||
|
spanned.span(),
|
||||||
|
Level::Error,
|
||||||
|
format!(
|
||||||
|
"{} does not allow fields with the same name",
|
||||||
|
self
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.help(format!("There is at least one other field with the same name `{}`, possibly renamed via the #[graphql] attribute", dup.name))
|
||||||
|
.note(self.specification_link())
|
||||||
|
.emit();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_double_underscore(&self, field: Span) {
|
||||||
|
Diagnostic::spanned(
|
||||||
|
field,
|
||||||
|
Level::Error,
|
||||||
|
"All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.".to_string(),
|
||||||
|
)
|
||||||
|
.note(format!("{}#sec-Schema", GRAPHQL_SPECIFICATION))
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
}
|
46
juniper_codegen/src/util/duplicate.rs
Normal file
46
juniper_codegen/src/util/duplicate.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct Duplicate<T> {
|
||||||
|
pub name: String,
|
||||||
|
pub spanned: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Duplicate<T> {
|
||||||
|
pub fn find_by_key<'a, F>(items: &'a [T], name: F) -> Option<Vec<Duplicate<&'a T>>>
|
||||||
|
where
|
||||||
|
T: 'a,
|
||||||
|
F: Fn(&'a T) -> &'a str,
|
||||||
|
{
|
||||||
|
let mut mapping: HashMap<&str, Vec<&T>> = HashMap::with_capacity(items.len());
|
||||||
|
|
||||||
|
for item in items {
|
||||||
|
if let Some(vals) = mapping.get_mut(name(item)) {
|
||||||
|
vals.push(item);
|
||||||
|
} else {
|
||||||
|
mapping.insert(name(item), vec![item]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let duplicates = mapping
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
if v.len() != 1 {
|
||||||
|
Some(Duplicate {
|
||||||
|
name: k.to_string(),
|
||||||
|
spanned: v,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if !duplicates.is_empty() {
|
||||||
|
Some(duplicates)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,17 @@
|
||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
|
pub mod duplicate;
|
||||||
pub mod parse_impl;
|
pub mod parse_impl;
|
||||||
|
pub mod span_container;
|
||||||
|
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use proc_macro_error::abort;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use span_container::SpanContainer;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse, parse_quote, punctuated::Punctuated, Attribute, Lit, Meta, MetaList, MetaNameValue,
|
parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Lit, Meta, MetaList,
|
||||||
NestedMeta, Token,
|
MetaNameValue, NestedMeta, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn juniper_path(is_internal: bool) -> syn::Path {
|
pub fn juniper_path(is_internal: bool) -> syn::Path {
|
||||||
|
@ -69,17 +74,6 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AttributeValidation {
|
|
||||||
Any,
|
|
||||||
// Bare,
|
|
||||||
String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AttributeValue {
|
|
||||||
Bare,
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DeprecationAttr {
|
pub struct DeprecationAttr {
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
|
@ -91,19 +85,22 @@ pub fn find_graphql_attr(attrs: &[Attribute]) -> Option<&Attribute> {
|
||||||
.find(|attr| path_eq_single(&attr.path, "graphql"))
|
.find(|attr| path_eq_single(&attr.path, "graphql"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_deprecated(attrs: &[Attribute]) -> Option<DeprecationAttr> {
|
pub fn get_deprecated(attrs: &[Attribute]) -> Option<SpanContainer<DeprecationAttr>> {
|
||||||
for attr in attrs {
|
attrs
|
||||||
match attr.parse_meta() {
|
.iter()
|
||||||
|
.filter_map(|attr| match attr.parse_meta() {
|
||||||
Ok(Meta::List(ref list)) if list.path.is_ident("deprecated") => {
|
Ok(Meta::List(ref list)) if list.path.is_ident("deprecated") => {
|
||||||
return Some(get_deprecated_meta_list(list));
|
let val = get_deprecated_meta_list(list);
|
||||||
|
Some(SpanContainer::new(list.path.span(), None, val))
|
||||||
}
|
}
|
||||||
Ok(Meta::Path(ref path)) if path.is_ident("deprecated") => {
|
Ok(Meta::Path(ref path)) if path.is_ident("deprecated") => Some(SpanContainer::new(
|
||||||
return Some(DeprecationAttr { reason: None });
|
path.span(),
|
||||||
}
|
None,
|
||||||
_ => {}
|
DeprecationAttr { reason: None },
|
||||||
}
|
)),
|
||||||
}
|
_ => None,
|
||||||
None
|
})
|
||||||
|
.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
||||||
|
@ -116,13 +113,16 @@ fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
||||||
reason: Some(strlit.value()),
|
reason: Some(strlit.value()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => panic!("deprecated attribute note value only has string literal"),
|
_ => abort!(syn::Error::new(
|
||||||
|
nv.lit.span(),
|
||||||
|
"only strings are allowed for deprecation",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
abort!(syn::Error::new(
|
||||||
"Unrecognized setting on #[deprecated(..)] attribute: {:?}",
|
nv.path.span(),
|
||||||
nv.path,
|
"unrecognized setting on #[deprecated(..)] attribute",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,10 +130,10 @@ fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets doc comment.
|
// Gets doc comment.
|
||||||
pub fn get_doc_comment(attrs: &[Attribute]) -> Option<String> {
|
pub fn get_doc_comment(attrs: &[Attribute]) -> Option<SpanContainer<String>> {
|
||||||
if let Some(items) = get_doc_attr(attrs) {
|
if let Some(items) = get_doc_attr(attrs) {
|
||||||
if let Some(doc_strings) = get_doc_strings(&items) {
|
if let Some(doc_strings) = get_doc_strings(&items) {
|
||||||
return Some(join_doc_strings(&doc_strings));
|
return Some(doc_strings.map(|strings| join_doc_strings(&strings)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
@ -167,25 +167,30 @@ fn join_doc_strings(docs: &[String]) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets doc strings from doc comment attributes.
|
// Gets doc strings from doc comment attributes.
|
||||||
fn get_doc_strings(items: &[MetaNameValue]) -> Option<Vec<String>> {
|
fn get_doc_strings(items: &[MetaNameValue]) -> Option<SpanContainer<Vec<String>>> {
|
||||||
|
let mut span = None;
|
||||||
let comments = items
|
let comments = items
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| {
|
.filter_map(|item| {
|
||||||
if item.path.is_ident("doc") {
|
if item.path.is_ident("doc") {
|
||||||
match item.lit {
|
match item.lit {
|
||||||
Lit::Str(ref strlit) => Some(strlit.value()),
|
Lit::Str(ref strlit) => {
|
||||||
_ => panic!("doc attributes only have string literal"),
|
if span.is_none() {
|
||||||
|
span = Some(strlit.span());
|
||||||
|
}
|
||||||
|
Some(strlit.value())
|
||||||
|
}
|
||||||
|
_ => abort!(syn::Error::new(
|
||||||
|
item.lit.span(),
|
||||||
|
"doc attributes only have string literal"
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if comments.is_empty() {
|
span.map(|span| SpanContainer::new(span, None, comments))
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(comments)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets doc comment attributes.
|
// Gets doc comment attributes.
|
||||||
|
@ -203,47 +208,6 @@ fn get_doc_attr(attrs: &[Attribute]) -> Option<Vec<MetaNameValue>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the nested items of a a #[graphql(...)] attribute.
|
|
||||||
pub fn get_graphql_attr(attrs: &[Attribute]) -> Option<Vec<NestedMeta>> {
|
|
||||||
for attr in attrs {
|
|
||||||
match attr.parse_meta() {
|
|
||||||
Ok(Meta::List(ref list)) if list.path.is_ident("graphql") => {
|
|
||||||
return Some(list.nested.iter().cloned().collect());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keyed_item_value(
|
|
||||||
item: &NestedMeta,
|
|
||||||
name: &str,
|
|
||||||
validation: AttributeValidation,
|
|
||||||
) -> Option<AttributeValue> {
|
|
||||||
match *item {
|
|
||||||
// Attributes in the form of `#[graphql(name = "value")]`.
|
|
||||||
NestedMeta::Meta(Meta::NameValue(ref nameval)) if nameval.path.is_ident(name) => {
|
|
||||||
match nameval.lit {
|
|
||||||
// We have a string attribute value.
|
|
||||||
Lit::Str(ref strlit) => Some(AttributeValue::String(strlit.value())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Attributes in the form of `#[graphql(name)]`.
|
|
||||||
NestedMeta::Meta(Meta::Path(ref path)) if path.is_ident(name) => match validation {
|
|
||||||
AttributeValidation::String => {
|
|
||||||
panic!(format!(
|
|
||||||
"Invalid format for attribute \"{:?}\": expected a string value",
|
|
||||||
item
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => Some(AttributeValue::Bare),
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: duplicated from juniper crate!
|
// Note: duplicated from juniper crate!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn to_camel_case(s: &str) -> String {
|
pub fn to_camel_case(s: &str) -> String {
|
||||||
|
@ -309,12 +273,12 @@ pub fn is_valid_name(field_name: &str) -> bool {
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ObjectAttributes {
|
pub struct ObjectAttributes {
|
||||||
pub name: Option<String>,
|
pub name: Option<SpanContainer<String>>,
|
||||||
pub description: Option<String>,
|
pub description: Option<SpanContainer<String>>,
|
||||||
pub context: Option<syn::Type>,
|
pub context: Option<SpanContainer<syn::Type>>,
|
||||||
pub scalar: Option<syn::Type>,
|
pub scalar: Option<SpanContainer<syn::Type>>,
|
||||||
pub interfaces: Vec<syn::Type>,
|
pub interfaces: Vec<SpanContainer<syn::Type>>,
|
||||||
pub no_async: bool,
|
pub no_async: Option<SpanContainer<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl syn::parse::Parse for ObjectAttributes {
|
impl syn::parse::Parse for ObjectAttributes {
|
||||||
|
@ -325,7 +289,7 @@ impl syn::parse::Parse for ObjectAttributes {
|
||||||
context: None,
|
context: None,
|
||||||
scalar: None,
|
scalar: None,
|
||||||
interfaces: Vec::new(),
|
interfaces: Vec::new(),
|
||||||
no_async: false,
|
no_async: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
|
@ -334,12 +298,20 @@ impl syn::parse::Parse for ObjectAttributes {
|
||||||
"name" => {
|
"name" => {
|
||||||
input.parse::<syn::Token![=]>()?;
|
input.parse::<syn::Token![=]>()?;
|
||||||
let val = input.parse::<syn::LitStr>()?;
|
let val = input.parse::<syn::LitStr>()?;
|
||||||
output.name = Some(val.value());
|
output.name = Some(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
Some(val.span()),
|
||||||
|
val.value(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
"description" => {
|
"description" => {
|
||||||
input.parse::<syn::Token![=]>()?;
|
input.parse::<syn::Token![=]>()?;
|
||||||
let val = input.parse::<syn::LitStr>()?;
|
let val = input.parse::<syn::LitStr>()?;
|
||||||
output.description = Some(val.value());
|
output.description = Some(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
Some(val.span()),
|
||||||
|
val.value(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
"context" | "Context" => {
|
"context" | "Context" => {
|
||||||
input.parse::<syn::Token![=]>()?;
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
@ -351,12 +323,12 @@ impl syn::parse::Parse for ObjectAttributes {
|
||||||
} else {
|
} else {
|
||||||
input.parse::<syn::Type>()?
|
input.parse::<syn::Type>()?
|
||||||
};
|
};
|
||||||
output.context = Some(ctx);
|
output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx));
|
||||||
}
|
}
|
||||||
"scalar" | "Scalar" => {
|
"scalar" | "Scalar" => {
|
||||||
input.parse::<syn::Token![=]>()?;
|
input.parse::<syn::Token![=]>()?;
|
||||||
let val = input.parse::<syn::Type>()?;
|
let val = input.parse::<syn::Type>()?;
|
||||||
output.scalar = Some(val);
|
output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val));
|
||||||
}
|
}
|
||||||
"interfaces" => {
|
"interfaces" => {
|
||||||
input.parse::<syn::Token![=]>()?;
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
@ -367,14 +339,17 @@ impl syn::parse::Parse for ObjectAttributes {
|
||||||
&content,
|
&content,
|
||||||
)?
|
)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.map(|interface| {
|
||||||
|
SpanContainer::new(ident.span(), Some(interface.span()), interface)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
// FIXME: make this unneccessary.
|
// FIXME: make this unneccessary.
|
||||||
"noasync" => {
|
"noasync" => {
|
||||||
output.no_async = true;
|
output.no_async = Some(SpanContainer::new(ident.span(), None, ()));
|
||||||
}
|
}
|
||||||
other => {
|
_ => {
|
||||||
return Err(input.error(format!("Unknown attribute: {}", other)));
|
return Err(syn::Error::new(ident.span(), "unknown attribute"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if input.lookahead1().peek(syn::Token![,]) {
|
if input.lookahead1().peek(syn::Token![,]) {
|
||||||
|
@ -409,6 +384,7 @@ impl ObjectAttributes {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FieldAttributeArgument {
|
pub struct FieldAttributeArgument {
|
||||||
pub name: syn::Ident,
|
pub name: syn::Ident,
|
||||||
|
pub rename: Option<SpanContainer<syn::LitStr>>,
|
||||||
pub default: Option<syn::Expr>,
|
pub default: Option<syn::Expr>,
|
||||||
pub description: Option<syn::LitStr>,
|
pub description: Option<syn::LitStr>,
|
||||||
}
|
}
|
||||||
|
@ -419,6 +395,7 @@ impl parse::Parse for FieldAttributeArgument {
|
||||||
|
|
||||||
let mut arg = Self {
|
let mut arg = Self {
|
||||||
name,
|
name,
|
||||||
|
rename: None,
|
||||||
default: None,
|
default: None,
|
||||||
description: None,
|
description: None,
|
||||||
};
|
};
|
||||||
|
@ -430,15 +407,17 @@ impl parse::Parse for FieldAttributeArgument {
|
||||||
content.parse::<Token![=]>()?;
|
content.parse::<Token![=]>()?;
|
||||||
|
|
||||||
match name.to_string().as_str() {
|
match name.to_string().as_str() {
|
||||||
|
"name" => {
|
||||||
|
let val: syn::LitStr = content.parse()?;
|
||||||
|
arg.rename = Some(SpanContainer::new(name.span(), Some(val.span()), val));
|
||||||
|
}
|
||||||
"description" => {
|
"description" => {
|
||||||
arg.description = Some(content.parse()?);
|
arg.description = Some(content.parse()?);
|
||||||
}
|
}
|
||||||
"default" => {
|
"default" => {
|
||||||
arg.default = Some(content.parse()?);
|
arg.default = Some(content.parse()?);
|
||||||
}
|
}
|
||||||
other => {
|
_ => return Err(syn::Error::new(name.span(), "unknown attribute")),
|
||||||
return Err(content.error(format!("Invalid attribute argument key {}", other)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard trailing comma.
|
// Discard trailing comma.
|
||||||
|
@ -456,11 +435,12 @@ pub enum FieldAttributeParseMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FieldAttribute {
|
enum FieldAttribute {
|
||||||
Name(syn::LitStr),
|
Name(SpanContainer<syn::LitStr>),
|
||||||
Description(syn::LitStr),
|
Description(SpanContainer<syn::LitStr>),
|
||||||
Deprecation(DeprecationAttr),
|
Deprecation(SpanContainer<DeprecationAttr>),
|
||||||
Skip(syn::Ident),
|
Skip(SpanContainer<syn::Ident>),
|
||||||
Arguments(HashMap<String, FieldAttributeArgument>),
|
Arguments(HashMap<String, FieldAttributeArgument>),
|
||||||
|
Default(SpanContainer<Option<syn::Expr>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl parse::Parse for FieldAttribute {
|
impl parse::Parse for FieldAttribute {
|
||||||
|
@ -473,30 +453,44 @@ impl parse::Parse for FieldAttribute {
|
||||||
let lit = input.parse::<syn::LitStr>()?;
|
let lit = input.parse::<syn::LitStr>()?;
|
||||||
let raw = lit.value();
|
let raw = lit.value();
|
||||||
if !is_valid_name(&raw) {
|
if !is_valid_name(&raw) {
|
||||||
Err(input.error(format!(
|
Err(syn::Error::new(lit.span(), "name consists of not allowed characters. (must match /^[_a-zA-Z][_a-zA-Z0-9]*$/)"))
|
||||||
"Invalid #[graphql(name = ...)] attribute: \n\
|
|
||||||
'{}' is not a valid field name\nNames must \
|
|
||||||
match /^[_a-zA-Z][_a-zA-Z0-9]*$/",
|
|
||||||
raw,
|
|
||||||
)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(FieldAttribute::Name(lit))
|
Ok(FieldAttribute::Name(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
Some(lit.span()),
|
||||||
|
lit,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"description" => {
|
"description" => {
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
Ok(FieldAttribute::Description(input.parse()?))
|
let lit = input.parse::<syn::LitStr>()?;
|
||||||
|
Ok(FieldAttribute::Description(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
Some(lit.span()),
|
||||||
|
lit,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
"deprecated" | "deprecation" => {
|
"deprecated" | "deprecation" => {
|
||||||
let reason = if input.peek(Token![=]) {
|
let reason = if input.peek(Token![=]) {
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
Some(input.parse::<syn::LitStr>()?.value())
|
Some(input.parse::<syn::LitStr>()?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok(FieldAttribute::Deprecation(DeprecationAttr { reason }))
|
Ok(FieldAttribute::Deprecation(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
reason.as_ref().map(|val| val.span()),
|
||||||
|
DeprecationAttr {
|
||||||
|
reason: reason.map(|val| val.value()),
|
||||||
|
},
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
"skip" => Ok(FieldAttribute::Skip(ident)),
|
"skip" => Ok(FieldAttribute::Skip(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
None,
|
||||||
|
ident,
|
||||||
|
))),
|
||||||
"arguments" => {
|
"arguments" => {
|
||||||
let arg_content;
|
let arg_content;
|
||||||
syn::parenthesized!(arg_content in input);
|
syn::parenthesized!(arg_content in input);
|
||||||
|
@ -509,20 +503,34 @@ impl parse::Parse for FieldAttribute {
|
||||||
.collect();
|
.collect();
|
||||||
Ok(FieldAttribute::Arguments(map))
|
Ok(FieldAttribute::Arguments(map))
|
||||||
}
|
}
|
||||||
other => Err(input.error(format!("Unknown attribute: {}", other))),
|
"default" => {
|
||||||
|
let default_expr = if input.peek(Token![=]) {
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
let lit = input.parse::<syn::LitStr>()?;
|
||||||
|
let default_expr = lit.parse::<syn::Expr>()?;
|
||||||
|
SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr))
|
||||||
|
} else {
|
||||||
|
SpanContainer::new(ident.span(), None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FieldAttribute::Default(default_expr))
|
||||||
|
}
|
||||||
|
_ => Err(syn::Error::new(ident.span(), "unknown attribute")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FieldAttributes {
|
pub struct FieldAttributes {
|
||||||
pub name: Option<String>,
|
pub name: Option<SpanContainer<String>>,
|
||||||
pub description: Option<String>,
|
pub description: Option<SpanContainer<String>>,
|
||||||
pub deprecation: Option<DeprecationAttr>,
|
pub deprecation: Option<SpanContainer<DeprecationAttr>>,
|
||||||
// Only relevant for GraphQLObject derive.
|
// Only relevant for GraphQLObject derive.
|
||||||
pub skip: bool,
|
pub skip: Option<SpanContainer<syn::Ident>>,
|
||||||
/// Only relevant for object macro.
|
/// Only relevant for object macro.
|
||||||
pub arguments: HashMap<String, FieldAttributeArgument>,
|
pub arguments: HashMap<String, FieldAttributeArgument>,
|
||||||
|
/// Only relevant for object input objects.
|
||||||
|
pub default: Option<SpanContainer<Option<syn::Expr>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl parse::Parse for FieldAttributes {
|
impl parse::Parse for FieldAttributes {
|
||||||
|
@ -533,27 +541,31 @@ impl parse::Parse for FieldAttributes {
|
||||||
name: None,
|
name: None,
|
||||||
description: None,
|
description: None,
|
||||||
deprecation: None,
|
deprecation: None,
|
||||||
skip: false,
|
skip: None,
|
||||||
arguments: Default::default(),
|
arguments: Default::default(),
|
||||||
|
default: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
match item {
|
match item {
|
||||||
FieldAttribute::Name(name) => {
|
FieldAttribute::Name(name) => {
|
||||||
output.name = Some(name.value());
|
output.name = Some(name.map(|val| val.value()));
|
||||||
}
|
}
|
||||||
FieldAttribute::Description(name) => {
|
FieldAttribute::Description(name) => {
|
||||||
output.description = Some(name.value());
|
output.description = Some(name.map(|val| val.value()));
|
||||||
}
|
}
|
||||||
FieldAttribute::Deprecation(attr) => {
|
FieldAttribute::Deprecation(attr) => {
|
||||||
output.deprecation = Some(attr);
|
output.deprecation = Some(attr);
|
||||||
}
|
}
|
||||||
FieldAttribute::Skip(_) => {
|
FieldAttribute::Skip(ident) => {
|
||||||
output.skip = true;
|
output.skip = Some(ident);
|
||||||
}
|
}
|
||||||
FieldAttribute::Arguments(args) => {
|
FieldAttribute::Arguments(args) => {
|
||||||
output.arguments = args;
|
output.arguments = args;
|
||||||
}
|
}
|
||||||
|
FieldAttribute::Default(expr) => {
|
||||||
|
output.default = Some(expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +579,7 @@ impl parse::Parse for FieldAttributes {
|
||||||
|
|
||||||
impl FieldAttributes {
|
impl FieldAttributes {
|
||||||
pub fn from_attrs(
|
pub fn from_attrs(
|
||||||
attrs: Vec<syn::Attribute>,
|
attrs: &[syn::Attribute],
|
||||||
_mode: FieldAttributeParseMode,
|
_mode: FieldAttributeParseMode,
|
||||||
) -> syn::parse::Result<Self> {
|
) -> syn::parse::Result<Self> {
|
||||||
let doc_comment = get_doc_comment(&attrs);
|
let doc_comment = get_doc_comment(&attrs);
|
||||||
|
@ -611,14 +623,23 @@ pub struct GraphQLTypeDefinitionField {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub deprecation: Option<DeprecationAttr>,
|
pub deprecation: Option<DeprecationAttr>,
|
||||||
pub args: Vec<GraphQLTypeDefinitionFieldArg>,
|
pub args: Vec<GraphQLTypeDefinitionFieldArg>,
|
||||||
pub resolver_code: proc_macro2::TokenStream,
|
pub resolver_code: TokenStream,
|
||||||
pub is_type_inferred: bool,
|
pub is_type_inferred: bool,
|
||||||
pub is_async: bool,
|
pub is_async: bool,
|
||||||
|
pub default: Option<TokenStream>,
|
||||||
|
pub span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unraw(s: &str) -> String {
|
impl syn::spanned::Spanned for GraphQLTypeDefinitionField {
|
||||||
use syn::ext::IdentExt;
|
fn span(&self) -> Span {
|
||||||
quote::format_ident!("{}", s).unraw().to_string()
|
self.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> syn::spanned::Spanned for &'a GraphQLTypeDefinitionField {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Definition of a graphql type based on information extracted
|
/// Definition of a graphql type based on information extracted
|
||||||
|
@ -656,7 +677,7 @@ impl GraphQLTypeDefiniton {
|
||||||
self.fields.iter().any(|field| field.is_async)
|
self.fields.iter().any(|field| field.is_async)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream {
|
pub fn into_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
@ -670,7 +691,7 @@ impl GraphQLTypeDefiniton {
|
||||||
let field_definitions = self.fields.iter().map(|field| {
|
let field_definitions = self.fields.iter().map(|field| {
|
||||||
let args = field.args.iter().map(|arg| {
|
let args = field.args.iter().map(|arg| {
|
||||||
let arg_type = &arg._type;
|
let arg_type = &arg._type;
|
||||||
let arg_name = unraw(&arg.name);
|
let arg_name = &arg.name;
|
||||||
|
|
||||||
let description = match arg.description.as_ref() {
|
let description = match arg.description.as_ref() {
|
||||||
Some(value) => quote!( .description( #value ) ),
|
Some(value) => quote!( .description( #value ) ),
|
||||||
|
@ -710,7 +731,7 @@ impl GraphQLTypeDefiniton {
|
||||||
None => quote!(),
|
None => quote!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let field_name = unraw(&field.name);
|
let field_name = &field.name;
|
||||||
|
|
||||||
let _type = &field._type;
|
let _type = &field._type;
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -929,7 +950,32 @@ impl GraphQLTypeDefiniton {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
// let marks = self.fields.iter().map(|field| {
|
||||||
|
// let field_ty = &field._type;
|
||||||
|
|
||||||
|
// let field_marks = field.args.iter().map(|arg| {
|
||||||
|
// let arg_ty = &arg._type;
|
||||||
|
// quote!(<#arg_ty as #juniper_crate_name::marker::IsInputType<#scalar>>::mark();)
|
||||||
|
// });
|
||||||
|
|
||||||
|
// quote!(
|
||||||
|
// #( #field_marks)*
|
||||||
|
// <#field_ty as #juniper_crate_name::marker::IsOutputType<#scalar>>::mark();
|
||||||
|
// )
|
||||||
|
// });
|
||||||
|
|
||||||
let output = quote!(
|
let output = quote!(
|
||||||
|
impl#impl_generics #juniper_crate_name::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause {
|
||||||
|
fn mark() {
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
// #( #marks )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl#impl_generics #juniper_crate_name::marker::GraphQLObjectType<#scalar> for #ty #type_generics_tokens #where_clause
|
||||||
|
{ }
|
||||||
|
|
||||||
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens
|
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
|
@ -987,7 +1033,7 @@ impl GraphQLTypeDefiniton {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_subscription_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream {
|
pub fn into_subscription_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
@ -1256,7 +1302,7 @@ impl GraphQLTypeDefiniton {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_union_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream {
|
pub fn into_union_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
@ -1425,9 +1471,22 @@ impl GraphQLTypeDefiniton {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let object_marks = self.fields.iter().map(|field| {
|
||||||
|
let _ty = &field._type;
|
||||||
|
quote!(
|
||||||
|
<#_ty as #juniper_crate_name::marker::GraphQLObjectType<#scalar>>::mark();
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut type_impl = quote! {
|
let mut type_impl = quote! {
|
||||||
#( #convesion_impls )*
|
#( #convesion_impls )*
|
||||||
|
|
||||||
|
impl #impl_generics #juniper_crate_name::marker::IsOutputType<#scalar> for #ty #where_clause {
|
||||||
|
fn mark() {
|
||||||
|
#( #object_marks )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl #impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #where_clause
|
impl #impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #where_clause
|
||||||
{
|
{
|
||||||
type Context = #context;
|
type Context = #context;
|
||||||
|
@ -1482,7 +1541,7 @@ impl GraphQLTypeDefiniton {
|
||||||
type_impl
|
type_impl
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_enum_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream {
|
pub fn into_enum_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
@ -1609,6 +1668,12 @@ impl GraphQLTypeDefiniton {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut body = quote!(
|
let mut body = quote!(
|
||||||
|
impl#impl_generics #juniper_crate_name::marker::IsInputType<#scalar> for #ty
|
||||||
|
#where_clause { }
|
||||||
|
|
||||||
|
impl#impl_generics #juniper_crate_name::marker::IsOutputType<#scalar> for #ty
|
||||||
|
#where_clause { }
|
||||||
|
|
||||||
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty
|
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
|
@ -1676,12 +1741,246 @@ impl GraphQLTypeDefiniton {
|
||||||
|
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_input_object_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
|
let name = &self.name;
|
||||||
|
let ty = &self._type;
|
||||||
|
let context = self
|
||||||
|
.context
|
||||||
|
.as_ref()
|
||||||
|
.map(|ctx| quote!( #ctx ))
|
||||||
|
.unwrap_or_else(|| quote!(()));
|
||||||
|
|
||||||
|
let scalar = self
|
||||||
|
.scalar
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote!( #s ))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
if self.generic_scalar {
|
||||||
|
// If generic_scalar is true, we always insert a generic scalar.
|
||||||
|
// See more comments below.
|
||||||
|
quote!(__S)
|
||||||
|
} else {
|
||||||
|
quote!(#juniper_crate_name::DefaultScalarValue)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let meta_fields = self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
// HACK: use a different interface for the GraphQLField?
|
||||||
|
let field_ty = &field._type;
|
||||||
|
let field_name = &field.name;
|
||||||
|
|
||||||
|
let description = match field.description.as_ref() {
|
||||||
|
Some(description) => quote!( .description(#description) ),
|
||||||
|
None => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let deprecation = match field.deprecation.as_ref() {
|
||||||
|
Some(deprecation) => {
|
||||||
|
if let Some(reason) = deprecation.reason.as_ref() {
|
||||||
|
quote!( .deprecated(Some(#reason)) )
|
||||||
|
} else {
|
||||||
|
quote!( .deprecated(None) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_meta_field = match field.default {
|
||||||
|
Some(ref def) => {
|
||||||
|
quote! {
|
||||||
|
registry.arg_with_default::<#field_ty>( #field_name, &#def, &())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote! {
|
||||||
|
registry.arg::<#field_ty>(#field_name, &())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote!(
|
||||||
|
{
|
||||||
|
#create_meta_field
|
||||||
|
#description
|
||||||
|
#deprecation
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let from_inputs = self.fields.iter().map(|field| {
|
||||||
|
let field_ident = &field.resolver_code;
|
||||||
|
let field_name = &field.name;
|
||||||
|
|
||||||
|
// Build from_input clause.
|
||||||
|
let from_input_default = match field.default {
|
||||||
|
Some(ref def) => {
|
||||||
|
quote! {
|
||||||
|
Some(&&#juniper_crate_name::InputValue::Null) | None if true => #def,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => quote! {},
|
||||||
|
};
|
||||||
|
|
||||||
|
quote!(
|
||||||
|
#field_ident: {
|
||||||
|
// TODO: investigate the unwraps here, they seem dangerous!
|
||||||
|
match obj.get(#field_name) {
|
||||||
|
#from_input_default
|
||||||
|
Some(ref v) => #juniper_crate_name::FromInputValue::from_input_value(v).unwrap(),
|
||||||
|
None => {
|
||||||
|
#juniper_crate_name::FromInputValue::from_input_value(&#juniper_crate_name::InputValue::<#scalar>::null())
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let to_inputs = self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let field_name = &field.name;
|
||||||
|
let field_ident = &field.resolver_code;
|
||||||
|
// Build to_input clause.
|
||||||
|
quote!(
|
||||||
|
(#field_name, self.#field_ident.to_input_value()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let description = self
|
||||||
|
.description
|
||||||
|
.as_ref()
|
||||||
|
.map(|description| quote!( .description(#description) ));
|
||||||
|
|
||||||
|
// Preserve the original type_generics before modification,
|
||||||
|
// since alteration makes them invalid if self.generic_scalar
|
||||||
|
// is specified.
|
||||||
|
let (_, type_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
|
let mut generics = self.generics.clone();
|
||||||
|
|
||||||
|
if self.scalar.is_none() && self.generic_scalar {
|
||||||
|
// No custom scalar specified, but always generic specified.
|
||||||
|
// Therefore we inject the generic scalar.
|
||||||
|
|
||||||
|
generics.params.push(parse_quote!(__S));
|
||||||
|
|
||||||
|
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
|
||||||
|
// Insert ScalarValue constraint.
|
||||||
|
where_clause
|
||||||
|
.predicates
|
||||||
|
.push(parse_quote!(__S: #juniper_crate_name::ScalarValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_generics_tokens = if self.include_type_generics {
|
||||||
|
Some(type_generics)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
|
||||||
|
|
||||||
|
where_async
|
||||||
|
.predicates
|
||||||
|
.push(parse_quote!( #scalar: Send + Sync ));
|
||||||
|
where_async.predicates.push(parse_quote!(Self: Send + Sync));
|
||||||
|
|
||||||
|
let async_type = quote!(
|
||||||
|
impl#impl_generics #juniper_crate_name::GraphQLTypeAsync<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_async
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
// let marks = self.fields.iter().map(|field| {
|
||||||
|
// let _ty = &field._type;
|
||||||
|
// quote!(<#_ty as #juniper_crate_name::marker::IsInputType<#scalar>>::mark();)
|
||||||
|
// });
|
||||||
|
|
||||||
|
let mut body = quote!(
|
||||||
|
impl#impl_generics #juniper_crate_name::marker::IsInputType<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_clause {
|
||||||
|
fn mark() {
|
||||||
|
// FIXME: enable this if interfaces are supported
|
||||||
|
// #( #marks )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
type Context = #context;
|
||||||
|
type TypeInfo = ();
|
||||||
|
|
||||||
|
fn name(_: &()) -> Option<&'static str> {
|
||||||
|
Some(#name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meta<'r>(
|
||||||
|
_: &(),
|
||||||
|
registry: &mut #juniper_crate_name::Registry<'r, #scalar>
|
||||||
|
) -> #juniper_crate_name::meta::MetaType<'r, #scalar>
|
||||||
|
where #scalar: 'r
|
||||||
|
{
|
||||||
|
let fields = &[
|
||||||
|
#( #meta_fields )*
|
||||||
|
];
|
||||||
|
registry.build_input_object_type::<#ty>(&(), fields)
|
||||||
|
#description
|
||||||
|
.into_meta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl#impl_generics #juniper_crate_name::FromInputValue<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn from_input_value(value: &#juniper_crate_name::InputValue<#scalar>) -> Option<Self>
|
||||||
|
{
|
||||||
|
if let Some(obj) = value.to_object_value() {
|
||||||
|
let item = #ty {
|
||||||
|
#( #from_inputs )*
|
||||||
|
};
|
||||||
|
Some(item)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl#impl_generics #juniper_crate_name::ToInputValue<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn to_input_value(&self) -> #juniper_crate_name::InputValue<#scalar> {
|
||||||
|
#juniper_crate_name::InputValue::object(vec![
|
||||||
|
#( #to_inputs )*
|
||||||
|
].into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if !self.no_async {
|
||||||
|
body.extend(async_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use quote::__private::*;
|
|
||||||
use syn::{Ident, LitStr};
|
use syn::{Ident, LitStr};
|
||||||
|
|
||||||
fn strs_to_strings(source: Vec<&str>) -> Vec<String> {
|
fn strs_to_strings(source: Vec<&str>) -> Vec<String> {
|
||||||
|
|
|
@ -1,38 +1,11 @@
|
||||||
//! Parse impl blocks.
|
//! Parse impl blocks.
|
||||||
#![allow(clippy::or_fun_call)]
|
#![allow(clippy::or_fun_call)]
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use crate::util::{self, span_container::SpanContainer};
|
||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::{convert::From, fmt};
|
use std::convert::From;
|
||||||
|
use syn::{spanned::Spanned, PatType};
|
||||||
use crate::util;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ResolveFnError(String);
|
|
||||||
|
|
||||||
impl From<&str> for ResolveFnError {
|
|
||||||
fn from(item: &str) -> Self {
|
|
||||||
ResolveFnError(item.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for ResolveFnError {
|
|
||||||
fn from(item: String) -> Self {
|
|
||||||
ResolveFnError(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ResolveFnError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ResolveFnError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
self.0.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ImplBlock {
|
pub struct ImplBlock {
|
||||||
pub attrs: util::ObjectAttributes,
|
pub attrs: util::ObjectAttributes,
|
||||||
|
@ -46,64 +19,96 @@ pub struct ImplBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImplBlock {
|
impl ImplBlock {
|
||||||
/// Check if the block has the special `resolve()` method.
|
/// Parse a `fn resolve()` method declaration found in most
|
||||||
pub fn has_resolve_method(&self) -> bool {
|
/// generators which rely on `impl` blocks.
|
||||||
self.methods.iter().any(|m| m.sig.ident == "resolve")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a 'fn resolve()' method declaration found in union or interface
|
|
||||||
/// `impl` blocks.
|
|
||||||
/// Returns the variable definitions needed for the resolve body.
|
|
||||||
pub fn parse_resolve_method(
|
pub fn parse_resolve_method(
|
||||||
&self,
|
&self,
|
||||||
method: &syn::ImplItemMethod,
|
method: &syn::ImplItemMethod,
|
||||||
) -> Result<Vec<proc_macro2::TokenStream>, ResolveFnError> {
|
) -> syn::Result<Vec<TokenStream>> {
|
||||||
if method.sig.ident != "resolve" {
|
if method.sig.ident != "resolve" {
|
||||||
return Err("Expect a method named 'fn resolve(...)".into());
|
return Err(syn::Error::new(
|
||||||
|
method.sig.ident.span(),
|
||||||
|
"expect the method named `resolve`",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
match &method.sig.output {
|
if let syn::ReturnType::Type(_, _) = &method.sig.output {
|
||||||
syn::ReturnType::Type(_, _) => {
|
return Err(syn::Error::new(
|
||||||
return Err("resolve() method must not have a declared return type".into());
|
method.sig.output.span(),
|
||||||
}
|
"method must not have a declared return type",
|
||||||
syn::ReturnType::Default => {}
|
));
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut arguments = method.sig.inputs.iter();
|
//NOTICE: `fn resolve()` is a subset of `fn <NAME>() -> <TYPE>`
|
||||||
|
self.parse_method(method, false, |captured, _, _| {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
captured.span(),
|
||||||
|
"only executor or context types are allowed",
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.map(|(tokens, _empty)| tokens)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify '&self' argument.
|
/// Parse a `fn <NAME>() -> <TYPE>` method declaration found in
|
||||||
match arguments.next() {
|
/// objects.
|
||||||
|
pub fn parse_method<
|
||||||
|
F: Fn(
|
||||||
|
&PatType,
|
||||||
|
&Ident,
|
||||||
|
bool,
|
||||||
|
) -> syn::Result<(TokenStream, util::GraphQLTypeDefinitionFieldArg)>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
method: &syn::ImplItemMethod,
|
||||||
|
is_self_optional: bool,
|
||||||
|
f: F,
|
||||||
|
) -> syn::Result<(Vec<TokenStream>, Vec<util::GraphQLTypeDefinitionFieldArg>)> {
|
||||||
|
let mut arguments = method.sig.inputs.iter().peekable();
|
||||||
|
|
||||||
|
// Verify `&self` argument.
|
||||||
|
match arguments.peek() {
|
||||||
Some(syn::FnArg::Receiver(rec)) => {
|
Some(syn::FnArg::Receiver(rec)) => {
|
||||||
|
let _consume = arguments.next();
|
||||||
if rec.reference.is_none() || rec.mutability.is_some() {
|
if rec.reference.is_none() || rec.mutability.is_some() {
|
||||||
panic!(
|
return Err(syn::Error::new(
|
||||||
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
rec.span(),
|
||||||
method.sig.ident
|
"invalid argument: did you mean `&self`?",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err("Expected a '&self' argument".into());
|
if !is_self_optional {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
method.sig.span(),
|
||||||
|
"expected a `&self` argument",
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut resolve_parts = Vec::new();
|
let mut resolve_parts = Vec::new();
|
||||||
|
let mut additional_arguments = Vec::new();
|
||||||
|
|
||||||
for arg in arguments {
|
for arg in arguments {
|
||||||
match arg {
|
match arg {
|
||||||
syn::FnArg::Receiver(_) => {
|
syn::FnArg::Receiver(_) => {
|
||||||
return Err(format!(
|
if !is_self_optional {
|
||||||
"Malformed method signature {}: self receiver must be the first argument",
|
return Err(syn::Error::new(
|
||||||
method.sig.ident
|
method.sig.ident.span(),
|
||||||
)
|
"self receiver must be the first argument",
|
||||||
.into());
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
syn::FnArg::Typed(captured) => {
|
syn::FnArg::Typed(captured) => {
|
||||||
let (arg_ident, _is_mut) = match &*captured.pat {
|
let (arg_ident, is_mut) = match &*captured.pat {
|
||||||
syn::Pat::Ident(ref pat_ident) => {
|
syn::Pat::Ident(ref pat_ident) => {
|
||||||
(&pat_ident.ident, pat_ident.mutability.is_some())
|
(&pat_ident.ident, pat_ident.mutability.is_some())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Invalid token for function argument");
|
return Err(syn::Error::new(
|
||||||
|
captured.pat.span(),
|
||||||
|
"expected identifier for function argument",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let context_type = self.attrs.context.as_ref();
|
let context_type = self.attrs.context.as_ref();
|
||||||
|
@ -114,7 +119,10 @@ impl ImplBlock {
|
||||||
}
|
}
|
||||||
// Make sure executor is specified as a reference.
|
// Make sure executor is specified as a reference.
|
||||||
else if util::type_is_identifier(&captured.ty, "Executor") {
|
else if util::type_is_identifier(&captured.ty, "Executor") {
|
||||||
panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?");
|
return Err(syn::Error::new(
|
||||||
|
captured.ty.span(),
|
||||||
|
"to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// Check for context arg.
|
// Check for context arg.
|
||||||
else if context_type
|
else if context_type
|
||||||
|
@ -128,37 +136,28 @@ impl ImplBlock {
|
||||||
// without a reference. (&Context)
|
// without a reference. (&Context)
|
||||||
else if context_type
|
else if context_type
|
||||||
.clone()
|
.clone()
|
||||||
.map(|ctx| ctx == &*captured.ty)
|
.map(|ctx| ctx.inner() == &*captured.ty)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Err(format!(
|
return Err(syn::Error::new(
|
||||||
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
captured.ty.span(),
|
||||||
quote!(captured.ty),
|
format!("to access the context, you need to specify the type as a reference.\nDid you mean &{}?", quote!(captured.ty)),
|
||||||
).into());
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err("Invalid argument for 'resolve' method: only executor or context are allowed".into());
|
let (tokens, ty) = f(captured, arg_ident, is_mut)?;
|
||||||
|
resolve_parts.push(tokens);
|
||||||
|
additional_arguments.push(ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(resolve_parts)
|
Ok((resolve_parts, additional_arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> ImplBlock {
|
pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> syn::Result<ImplBlock> {
|
||||||
let attrs = match syn::parse::<util::ObjectAttributes>(attr_tokens) {
|
let attrs = syn::parse2::<util::ObjectAttributes>(attr_tokens)?;
|
||||||
Ok(attrs) => attrs,
|
let mut _impl = syn::parse2::<syn::ItemImpl>(body)?;
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid attributes:\n{}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut _impl = match syn::parse::<syn::ItemImpl>(body) {
|
|
||||||
Ok(item) => item,
|
|
||||||
Err(err) => {
|
|
||||||
panic!("Parsing error:\n{}", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let target_trait = match _impl.clone().trait_ {
|
let target_trait = match _impl.clone().trait_ {
|
||||||
Some((_, path, _)) => {
|
Some((_, path, _)) => {
|
||||||
|
@ -176,7 +175,10 @@ impl ImplBlock {
|
||||||
let type_ident = if let Some(ident) = util::name_of_type(&*_impl.self_ty) {
|
let type_ident = if let Some(ident) = util::name_of_type(&*_impl.self_ty) {
|
||||||
ident
|
ident
|
||||||
} else {
|
} else {
|
||||||
panic!("Could not determine a name for the impl type");
|
return Err(syn::Error::new(
|
||||||
|
_impl.self_ty.span(),
|
||||||
|
"could not determine a name for the impl type",
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_type = _impl.self_ty.clone();
|
let target_type = _impl.self_ty.clone();
|
||||||
|
@ -194,19 +196,22 @@ impl ImplBlock {
|
||||||
methods.push(method);
|
methods.push(method);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Invalid item: only type declarations and methods are allowed");
|
return Err(syn::Error::new(
|
||||||
|
item.span(),
|
||||||
|
"only type declarations and methods are allowed",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
attrs,
|
attrs,
|
||||||
type_ident,
|
type_ident,
|
||||||
target_trait,
|
target_trait,
|
||||||
target_type,
|
target_type,
|
||||||
generics: _impl.generics,
|
generics: _impl.generics,
|
||||||
description,
|
description: description.map(SpanContainer::into_inner),
|
||||||
methods,
|
methods,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
70
juniper_codegen/src/util/span_container.rs
Normal file
70
juniper_codegen/src/util/span_container.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::ToTokens;
|
||||||
|
use std::cmp::{Eq, PartialEq};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SpanContainer<T> {
|
||||||
|
expr: Option<Span>,
|
||||||
|
ident: Span,
|
||||||
|
val: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToTokens> ToTokens for SpanContainer<T> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
self.val.to_tokens(tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SpanContainer<T> {
|
||||||
|
pub fn new(ident: Span, expr: Option<Span>, val: T) -> Self {
|
||||||
|
Self { ident, expr, val }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span_ident(&self) -> Span {
|
||||||
|
self.ident
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &T {
|
||||||
|
&self.val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> SpanContainer<U> {
|
||||||
|
SpanContainer {
|
||||||
|
expr: self.expr,
|
||||||
|
ident: self.ident,
|
||||||
|
val: f(self.val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<T> for SpanContainer<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
&self.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for SpanContainer<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> PartialEq for SpanContainer<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.val == other.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq> Eq for SpanContainer<T> {}
|
||||||
|
|
||||||
|
impl<T: PartialEq> PartialEq<T> for SpanContainer<T> {
|
||||||
|
fn eq(&self, other: &T) -> bool {
|
||||||
|
&self.val == other
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue