diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5319058..2da7b27c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,15 +21,16 @@ jobs: ################ pr: - if: ${{ github.event_name == 'pull_request' - && !contains(github.event.head_commit.message, '[skip ci]') }} + if: ${{ github.event_name == 'pull_request' }} needs: + - bench - clippy - example - feature - release-check - rustfmt - test + - test-book - wasm runs-on: ubuntu-latest steps: @@ -43,9 +44,6 @@ jobs: ########################## clippy: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -58,9 +56,6 @@ jobs: - run: make cargo.lint rustfmt: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -79,10 +74,20 @@ jobs: # Testing # ########### + bench: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - run: cargo clippy -p juniper_benchmarks --benches -- -D warnings + - run: cargo bench -p juniper_benchmarks + example: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: fail-fast: false matrix: @@ -111,9 +116,6 @@ jobs: - run: cargo check -p example_${{ matrix.example }} feature: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: fail-fast: false matrix: @@ -174,9 +176,6 @@ jobs: - run: cargo package -p ${{ steps.crate.outputs.NAME }} test: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: fail-fast: false matrix: @@ -187,7 +186,6 @@ jobs: - juniper_graphql_ws - juniper_integration_tests - juniper_codegen_tests - - juniper_book_tests - juniper_actix - juniper_hyper - juniper_iron @@ -210,14 +208,6 @@ jobs: os: macOS - crate: juniper_codegen_tests os: windows - - crate: juniper_book_tests - toolchain: beta - - crate: juniper_book_tests - toolchain: nightly - # TODO: LLVM ERROR: out of memory - - crate: juniper_integration_tests - os: windows - runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v3 @@ -229,10 +219,35 @@ jobs: - run: make test.cargo crate=${{ matrix.crate }} + test-book: + name: test Book + strategy: + fail-fast: false + matrix: + os: + - ubuntu + - macOS + # TODO: Re-enable once rust-lang/rust#99466 is fixed: + # https://github.com/rust-lang/rust/issues/99466 + #- windows + toolchain: + - stable + - beta + - nightly + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + + - run: cargo install mdbook + + - run: make test.book + wasm: - if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper') - || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: fail-fast: false matrix: @@ -264,9 +279,7 @@ jobs: release-check: name: Check release automation - if: ${{ !startsWith(github.ref, 'refs/tags/juniper') - && (github.ref == 'refs/heads/master' - || !contains(github.event.head_commit.message, '[skip ci]')) }} + if: ${{ !startsWith(github.ref, 'refs/tags/juniper') }} strategy: fail-fast: false matrix: @@ -296,12 +309,14 @@ jobs: release-github: name: Release on GitHub needs: + - bench - clippy - example - feature - package - rustfmt - test + - test-book - wasm if: ${{ startsWith(github.ref, 'refs/tags/juniper') }} runs-on: ubuntu-latest @@ -325,7 +340,7 @@ jobs: - name: Parse CHANGELOG link id: changelog run: echo ::set-output - name=LINK::https://github.com/${{ github.repository }}/blob/${{ steps.crate.outputs.NAME }}%40${{ steps.release.outputs.VERSION }}//${{ steps.crate.outputs.NAME }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.release.outputs.VERSION }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.crate.outputs.NAME }}/CHANGELOG.md) + name=LINK::${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.crate.outputs.NAME }}%40${{ steps.release.outputs.VERSION }}//${{ steps.crate.outputs.NAME }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.release.outputs.VERSION }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.crate.outputs.NAME }}/CHANGELOG.md) - uses: softprops/action-gh-release@v1 env: @@ -368,19 +383,17 @@ jobs: deploy-book: name: deploy Book - needs: ["test"] + needs: ["test", "test-book"] if: ${{ github.ref == 'refs/heads/master' - || startsWith(github.ref, 'refs/tags/juniper@') }} + || startsWith(github.ref, 'refs/tags/juniper@') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: peaceiris/actions-mdbook@v1 - - run: make book.build out=gh-pages/master - if: ${{ github.ref == 'refs/heads/master' }} - - - run: make book.build out=gh-pages - if: ${{ startsWith(github.ref, 'refs/tags/juniper@') }} + - run: make book.build out=gh-pages${{ (github.ref == 'refs/heads/master' + && '/master') + || '' }} - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 974fe42f..015f03f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,19 +17,16 @@ Before submitting a PR, you should follow these steps to prevent redundant churn Consistent formatting is enforced on the CI. -Before you submit your PR, you should run `cargo fmt` in the root directory. +Before you submit your PR, you should run `cargo +nightly fmt --all` in the root directory (or use the `make fmt` shortcut). -Formatting should be run on the **stable** compiler. -(You can do `rustup run stable cargo fmt` when developing on nightly) +Formatting should be run on the **nightly** compiler. ### Run all tests -To run all available tests, including verifying the code examples in the book, -you can use [cargo-make](https://github.com/sagiegurari/cargo-make). +To run all available tests, including verifying the code examples in the book: -1. Install cargo-make with `cargo install cargo-make` -2. Run `cargo make ci-flow` in the root directory - (You can do `rustup run nightly cargo make ci-flow` to run all tests when developing on stable) +1. Run `cargo test` in the root directory. +2. Run `make test.book` in the root directory. ### Update the CHANGELOG diff --git a/Cargo.toml b/Cargo.toml index 1a97d16a..9b94e2fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "benches", - "book/tests", "examples/basic_subscriptions", "examples/warp_async", "examples/warp_subscriptions", diff --git a/Makefile b/Makefile index e6a566bf..7f5755be 100644 --- a/Makefile +++ b/Makefile @@ -87,10 +87,14 @@ cargo.test: test.cargo # Run Rust tests of Book. # # Usage: -# make test.book +# make test.book [clean=(no|yes)] test.book: - @make test.cargo crate=juniper_book_tests +ifeq ($(clean),yes) + cargo clean +endif + cargo build + mdbook test book -L target/debug/deps # Run Rust tests of project crates. diff --git a/README.md b/README.md index 3bd50048..5a028b05 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ see the [actix][actix_examples], [hyper][hyper_examples], [rocket][rocket_exampl ## Features Juniper supports the full GraphQL query language according to the -[specification][graphql_spec], including interfaces, unions, schema +[specification (October 2021)][graphql_spec], including interfaces, unions, schema introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language]. As an exception to other GraphQL libraries for other languages, Juniper builds @@ -97,7 +97,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [graphiql]: https://github.com/graphql/graphiql [playground]: https://github.com/prisma/graphql-playground [iron]: https://github.com/iron/iron -[graphql_spec]: http://facebook.github.io/graphql +[graphql_spec]: https://spec.graphql.org/October2021 [schema_language]: https://graphql.org/learn/schema/#type-language [schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/ [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/fixtures/starwars/schema.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index c5014f5a..1cabd057 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "juniper_benchmarks" version = "0.0.0" -edition = "2018" +edition = "2021" authors = ["Christoph Herzog "] publish = false @@ -10,7 +10,7 @@ futures = "0.3" juniper = { path = "../juniper" } [dev-dependencies] -criterion = "0.3" +criterion = "0.4" tokio = { version = "1.0", features = ["rt-multi-thread"] } [[bench]] diff --git a/benches/benches/benchmark.rs b/benches/benches/benchmark.rs index 7aa5da65..b6f82773 100644 --- a/benches/benches/benchmark.rs +++ b/benches/benches/benchmark.rs @@ -1,10 +1,11 @@ -use criterion::{criterion_group, criterion_main, Criterion, ParameterizedBenchmark}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use juniper::InputValue; use juniper_benchmarks as j; fn bench_sync_vs_async_users_flat_instant(c: &mut Criterion) { - const ASYNC_QUERY: &'static str = r#" + // language=GraphQL + const ASYNC_QUERY: &str = r#" query Query($id: Int) { users_async_instant(ids: [$id]!) { id @@ -15,74 +16,70 @@ fn bench_sync_vs_async_users_flat_instant(c: &mut Criterion) { } "#; - const SYNC_QUERY: &'static str = r#" - query Query($id: Int) { - users_sync_instant(ids: [$id]!) { - id - kind - username - email + // language=GraphQL + const SYNC_QUERY: &str = r#" + query Query($id: Int) { + users_sync_instant(ids: [$id]!) { + id + kind + username + email + } } + "#; + + let mut group = c.benchmark_group("Sync vs Async - Users Flat - Instant"); + for count in [1, 10] { + group.bench_function(BenchmarkId::new("Sync", count), |b| { + let ids = (0..count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + b.iter(|| { + j::execute_sync( + SYNC_QUERY, + vec![("ids".to_owned(), ids.clone())].into_iter().collect(), + ) + }) + }); + + group.bench_function(BenchmarkId::new("Async - Single Thread", count), |b| { + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); + + let ids = (0..count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute( + ASYNC_QUERY, + vec![("ids".to_owned(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }); + + group.bench_function(BenchmarkId::new("Async - Threadpool", count), |b| { + let rt = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + + let ids = (0..count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute( + ASYNC_QUERY, + vec![("ids".to_owned(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }); } -"#; - - c.bench( - "Sync vs Async - Users Flat - Instant", - ParameterizedBenchmark::new( - "Sync", - |b, count| { - let ids = (0..*count) - .map(|x| InputValue::scalar(x as i32)) - .collect::>(); - let ids = InputValue::list(ids); - b.iter(|| { - j::execute_sync( - SYNC_QUERY, - vec![("ids".to_string(), ids.clone())].into_iter().collect(), - ) - }) - }, - vec![1, 10], - ) - .with_function("Async - Single Thread", |b, count| { - let mut rt = tokio::runtime::Builder::new() - .basic_scheduler() - .build() - .unwrap(); - - let ids = (0..*count) - .map(|x| InputValue::scalar(x as i32)) - .collect::>(); - let ids = InputValue::list(ids); - - b.iter(|| { - let f = j::execute( - ASYNC_QUERY, - vec![("ids".to_string(), ids.clone())].into_iter().collect(), - ); - rt.block_on(f) - }) - }) - .with_function("Async - Threadpool", |b, count| { - let mut rt = tokio::runtime::Builder::new() - .threaded_scheduler() - .build() - .unwrap(); - - let ids = (0..*count) - .map(|x| InputValue::scalar(x as i32)) - .collect::>(); - let ids = InputValue::list(ids); - - b.iter(|| { - let f = j::execute( - ASYNC_QUERY, - vec![("ids".to_string(), ids.clone())].into_iter().collect(), - ); - rt.block_on(f) - }) - }), - ); + group.finish(); } criterion_group!(benches, bench_sync_vs_async_users_flat_instant); diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 526d0043..c8ad5b51 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -11,11 +11,11 @@ pub type QueryResult = Result< String, >; -pub struct Context {} +pub struct Context; impl Context { fn new() -> Self { - Self {} + Self } } @@ -51,8 +51,8 @@ impl User { Self { id, kind: UserKind::Admin, - username: "userx".to_string(), - email: "userx@domain.com".to_string(), + username: "userx".into(), + email: "userx@domain.com".into(), gender: Some(Gender::Female), } } @@ -97,7 +97,7 @@ pub fn new_schema() -> RootNode<'static, Query, EmptyMutation, EmptySub pub fn execute_sync(query: &str, vars: Variables) -> QueryResult { let root = new_schema(); let ctx = Context::new(); - juniper::execute_sync(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e)) + juniper::execute_sync(query, None, &root, &vars, &ctx).map_err(|e| format!("{e:?}")) } pub async fn execute(query: &str, vars: Variables) -> QueryResult { @@ -105,5 +105,5 @@ pub async fn execute(query: &str, vars: Variables) -> QueryResult { let ctx = Context::new(); juniper::execute(query, None, &root, &vars, &ctx) .await - .map_err(|e| format!("{:?}", e)) + .map_err(|e| format!("{e:?}")) } diff --git a/book/README.md b/book/README.md index f4b28d98..93ac3407 100644 --- a/book/README.md +++ b/book/README.md @@ -47,21 +47,8 @@ The output will be in the `_rendered/` directory. To run the tests validating all code examples in the book, run: ```bash -cd tests/ -cargo test - -# or from project root dir: -cargo test -p juniper_book_tests +mdbook test -L ../target/debug/deps # or via shortcut from project root dir: make test.book ``` - - - - -## Test setup - -All Rust code examples in the book are compiled on the CI. - -This is done using the [skeptic](https://github.com/budziq/rust-skeptic) crate. diff --git a/book/book.toml b/book/book.toml index 9fc67124..9dad3932 100644 --- a/book/book.toml +++ b/book/book.toml @@ -13,4 +13,4 @@ create-missing = false git_repository_url = "https://github.com/graphql-rs/juniper" [rust] -edition = "2018" +edition = "2021" diff --git a/book/src/README.md b/book/src/README.md index 07968a55..367f16cd 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -21,7 +21,7 @@ embedded [Graphiql][graphiql] for easy debugging. ## Features Juniper supports the full GraphQL query language according to the -[specification][graphql_spec], including interfaces, unions, schema +[specification (October 2021)][graphql_spec], including interfaces, unions, schema introspection, and validations. It does not, however, support the schema language. @@ -57,7 +57,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [graphql]: http://graphql.org [graphiql]: https://github.com/graphql/graphiql [iron]: https://github.com/iron/iron -[graphql_spec]: http://facebook.github.io/graphql +[graphql_spec]: https://spec.graphql.org/October2021 [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs [tokio]: https://github.com/tokio-rs/tokio [hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples diff --git a/book/src/advanced/dataloaders.md b/book/src/advanced/dataloaders.md index 560f6048..ef03afc1 100644 --- a/book/src/advanced/dataloaders.md +++ b/book/src/advanced/dataloaders.md @@ -68,7 +68,7 @@ use std::env; pub fn get_db_conn() -> Connection { let pg_connection_string = env::var("DATABASE_URI").expect("need a db uri"); - println!("Connecting to {}", pg_connection_string); + println!("Connecting to {pg_connection_string}"); let conn = Connection::connect(&pg_connection_string[..], TlsMode::None).unwrap(); println!("Connection is fine"); conn @@ -101,7 +101,7 @@ impl BatchFn for CultBatcher { // A hashmap is used, as we need to return an array which maps each original key to a Cult. async fn load(&self, keys: &[i32]) -> HashMap { - println!("load cult batch {:?}", keys); + println!("load cult batch {keys:?}"); let mut cult_hashmap = HashMap::new(); get_cult_by_ids(&mut cult_hashmap, keys.to_vec()); cult_hashmap diff --git a/book/src/advanced/introspection.md b/book/src/advanced/introspection.md index 3fabcb73..d7bc7c2f 100644 --- a/book/src/advanced/introspection.md +++ b/book/src/advanced/introspection.md @@ -66,7 +66,7 @@ type Schema = juniper::RootNode< fn main() { // Create a context object. - let ctx = Context{}; + let ctx = Context; // Run the built-in introspection query. let (res, _errors) = juniper::introspect( diff --git a/book/src/advanced/subscriptions.md b/book/src/advanced/subscriptions.md index 90877c42..51200b10 100644 --- a/book/src/advanced/subscriptions.md +++ b/book/src/advanced/subscriptions.md @@ -25,9 +25,11 @@ This example shows a subscription operation that returns two events, the strings sequentially: ```rust -# use juniper::{graphql_object, graphql_subscription, FieldError}; -# use futures::Stream; +# extern crate futures; +# extern crate juniper; # use std::pin::Pin; +# use futures::Stream; +# use juniper::{graphql_object, graphql_subscription, FieldError}; # # #[derive(Clone)] # pub struct Database; @@ -80,7 +82,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati # extern crate juniper; # extern crate juniper_subscriptions; # extern crate serde_json; -# extern crate tokio; # use juniper::{ # http::GraphQLRequest, # graphql_object, graphql_subscription, @@ -98,7 +99,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati # # impl Database { # fn new() -> Self { -# Self {} +# Self # } # } # @@ -126,7 +127,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati type Schema = RootNode<'static, Query, EmptyMutation, Subscription>; fn schema() -> Schema { - Schema::new(Query {}, EmptyMutation::new(), Subscription {}) + Schema::new(Query, EmptyMutation::new(), Subscription) } async fn run_subscription() { diff --git a/book/src/quickstart.md b/book/src/quickstart.md index b8ff7226..770b86a6 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -130,7 +130,7 @@ impl Mutation { type Schema = juniper::RootNode<'static, Query, Mutation, EmptySubscription>; # # fn main() { -# let _ = Schema::new(Query, Mutation{}, EmptySubscription::new()); +# let _ = Schema::new(Query, Mutation, EmptySubscription::new()); # } ``` diff --git a/book/src/servers/iron.md b/book/src/servers/iron.md index 9c81fc4b..38782c36 100644 --- a/book/src/servers/iron.md +++ b/book/src/servers/iron.md @@ -50,7 +50,7 @@ struct Root; #[juniper::graphql_object] impl Root { fn foo() -> String { - "Bar".to_owned() + "Bar".into() } } diff --git a/book/src/types/interfaces.md b/book/src/types/interfaces.md index aa9c543e..87b72348 100644 --- a/book/src/types/interfaces.md +++ b/book/src/types/interfaces.md @@ -77,6 +77,190 @@ struct Human { ``` +### Interfaces implementing other interfaces + +GraphQL allows implementing interfaces on other interfaces in addition to objects. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, graphql_object, ID}; + +#[graphql_interface(for = [HumanValue, Luke])] +struct Node { + id: ID, +} + +#[graphql_interface(impl = NodeValue, for = Luke)] +struct Human { + id: ID, + home_planet: String, +} + +struct Luke { + id: ID, +} + +#[graphql_object(impl = [HumanValue, NodeValue])] +impl Luke { + fn id(&self) -> &ID { + &self.id + } + + // As `String` and `&str` aren't distinguished by + // GraphQL spec, you can use them interchangeably. + // Same is applied for `Cow<'a, str>`. + // ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ + fn home_planet() -> &'static str { + "Tatooine" + } +} +# +# fn main() {} +``` + +> __NOTE:__ Every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile-time error. + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = ObjA)] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.' +struct Character { + id: String, +} + +fn main() {} +``` + + +### GraphQL subtyping and additional `null`able fields + +GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically, this allows you to impose additional bounds on the implementation. + +Valid "subtypes" are: +- interface implementer instead of an interface itself: + - `I implements T` in place of a `T`; + - `Vec` in place of a `Vec`. +- non-null value in place of a nullable: + - `T` in place of a `Option`; + - `Vec` in place of a `Vec>`. + +These rules are recursively applied, so `Vec>` is a valid "subtype" of a `Option>>>>`. + +Also, GraphQL allows implementers to add `null`able fields, which aren't present on an original interface. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, graphql_object, ID}; + +#[graphql_interface(for = [HumanValue, Luke])] +struct Node { + id: ID, +} + +#[graphql_interface(for = HumanConnectionValue)] +struct Connection { + nodes: Vec, +} + +#[graphql_interface(impl = NodeValue, for = Luke)] +struct Human { + id: ID, + home_planet: String, +} + +#[graphql_interface(impl = ConnectionValue)] +struct HumanConnection { + nodes: Vec, + // ^^^^^^^^^^ notice not `NodeValue` + // This can happen, because every `Human` is a `Node` too, so we are just + // imposing additional bounds, which still can be resolved with + // `... on Connection { nodes }`. +} + +struct Luke { + id: ID, +} + +#[graphql_object(impl = [HumanValue, NodeValue])] +impl Luke { + fn id(&self) -> &ID { + &self.id + } + + fn home_planet(language: Option) -> &'static str { + // ^^^^^^^^^^^^^^ + // Notice additional `null`able field, which is missing on `Human`. + // Resolving `...on Human { homePlanet }` will provide `None` for this + // argument. + match language.as_deref() { + None | Some("en") => "Tatooine", + Some("ko") => "타투인", + _ => todo!(), + } + } +} +# +# fn main() {} +``` + +Violating GraphQL "subtyping" or additional nullable field rules is a compile-time error. + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { +// ^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` +// isn't present on the interface and so has to be nullable.' + is_present.then_some(&self.id).unwrap_or("missing") + } +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} +# +# fn main() {} +``` + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +// ^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of +// interface's return object: `[String!]!` is not a subtype of `String!`.' +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} +# +# fn main() {} +``` + + ### Ignoring trait methods We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. @@ -289,9 +473,9 @@ struct Droid { -[1]: https://spec.graphql.org/June2018/#sec-Interfaces +[1]: https://spec.graphql.org/October2021#sec-Interfaces [2]: https://doc.rust-lang.org/reference/types/trait-object.html [3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html [4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html -[5]: https://spec.graphql.org/June2018/#sec-Objects +[5]: https://spec.graphql.org/October2021#sec-Objects [6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html diff --git a/book/src/types/objects/complex_fields.md b/book/src/types/objects/complex_fields.md index 86d310ec..f8aa1399 100644 --- a/book/src/types/objects/complex_fields.md +++ b/book/src/types/objects/complex_fields.md @@ -157,7 +157,7 @@ They can have custom descriptions and default values. # extern crate juniper; # use juniper::graphql_object; # -struct Person {} +struct Person; #[graphql_object] impl Person { @@ -177,7 +177,7 @@ impl Person { #[graphql(default)] arg2: i32, ) -> String { - format!("{} {}", arg1, arg2) + format!("{arg1} {arg2}") } } # diff --git a/book/src/types/objects/error_handling.md b/book/src/types/objects/error_handling.md index 4adc269f..5404c4bf 100644 --- a/book/src/types/objects/error_handling.md +++ b/book/src/types/objects/error_handling.md @@ -60,7 +60,7 @@ there - those errors are automatically converted into `FieldError`. ## Error payloads, `null`, and partial errors -Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability). +Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/October2021#sec-Handling-Field-Errors). When a field returns an error, the field's result is replaced by `null`, an additional `errors` object is created at the top level of the response, and the @@ -168,7 +168,7 @@ impl Example { # fn main() {} ``` -The specified structured error information is included in the [`extensions`](https://facebook.github.io/graphql/June2018/#sec-Errors) key: +The specified structured error information is included in the [`extensions`](https://spec.graphql.org/October2021#sec-Errors) key: ```json { @@ -242,15 +242,15 @@ impl Mutation { if !(10 <= name.len() && name.len() <= 100) { errors.push(ValidationError { - field: "name".to_string(), - message: "between 10 and 100".to_string() + field: "name".into(), + message: "between 10 and 100".into(), }); } if !(1 <= quantity && quantity <= 10) { errors.push(ValidationError { - field: "quantity".to_string(), - message: "between 1 and 10".to_string() + field: "quantity".into(), + message: "between 1 and 10".into(), }); } @@ -338,11 +338,11 @@ impl Mutation { }; if !(10 <= name.len() && name.len() <= 100) { - error.name = Some("between 10 and 100".to_string()); + error.name = Some("between 10 and 100".into()); } if !(1 <= quantity && quantity <= 10) { - error.quantity = Some("between 1 and 10".to_string()); + error.quantity = Some("between 1 and 10".into()); } if error.name.is_none() && error.quantity.is_none() { @@ -436,11 +436,11 @@ impl Mutation { }; if !(10 <= name.len() && name.len() <= 100) { - error.name = Some("between 10 and 100".to_string()); + error.name = Some("between 10 and 100".into()); } if !(1 <= quantity && quantity <= 10) { - error.quantity = Some("between 1 and 10".to_string()); + error.quantity = Some("between 1 and 10".into()); } if error.name.is_none() && error.quantity.is_none() { diff --git a/book/src/types/objects/using_contexts.md b/book/src/types/objects/using_contexts.md index 027b61a6..a795e09c 100644 --- a/book/src/types/objects/using_contexts.md +++ b/book/src/types/objects/using_contexts.md @@ -97,6 +97,7 @@ Context cannot be specified by a mutable reference, because concurrent fields re For example, when using async runtime with [work stealing][2] (like `tokio`), which obviously requires thread safety in addition, you will need to use a corresponding async version of `RwLock`: ```rust # extern crate juniper; +# extern crate tokio; # use std::collections::HashMap; # use juniper::graphql_object; use tokio::sync::RwLock; diff --git a/book/src/types/scalars.md b/book/src/types/scalars.md index a17e07a2..8fbe88ed 100644 --- a/book/src/types/scalars.md +++ b/book/src/types/scalars.md @@ -24,10 +24,10 @@ Juniper has built-in support for: * `String` and `&str` as `String` * `bool` as `Boolean` * `juniper::ID` as `ID`. This type is defined [in the - spec](http://facebook.github.io/graphql/#sec-ID) as a type that is serialized + spec](https://spec.graphql.org/October2021#sec-ID) as a type that is serialized as a string but can be parsed from both a string and an integer. -Note that there is no built-in support for `i64`/`u64`, as the GraphQL spec [doesn't define any built-in scalars for `i64`/`u64` by default](https://spec.graphql.org/June2018/#sec-Int). You may wish to leverage a [custom GraphQL scalar](#custom-scalars) in your schema to support them. +Note that there is no built-in support for `i64`/`u64`, as the GraphQL spec [doesn't define any built-in scalars for `i64`/`u64` by default](https://spec.graphql.org/October2021#sec-Int). You may wish to leverage a [custom GraphQL scalar](#custom-scalars) in your schema to support them. **Third party types**: @@ -114,6 +114,7 @@ All the methods used from newtype's field can be replaced with attributes: ### `#[graphql(to_output_with = )]` attribute ```rust +# extern crate juniper; # use juniper::{GraphQLScalar, ScalarValue, Value}; # #[derive(GraphQLScalar)] @@ -131,6 +132,7 @@ fn to_output(v: &Incremented) -> Value { ### `#[graphql(from_input_with = )]` attribute ```rust +# extern crate juniper; # use juniper::{GraphQLScalar, InputValue, ScalarValue}; # #[derive(GraphQLScalar)] @@ -145,14 +147,13 @@ impl UserId { S: ScalarValue { input.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", input)) + .ok_or_else(|| format!("Expected `String`, found: {input}")) .and_then(|str| { str.strip_prefix("id: ") .ok_or_else(|| { format!( "Expected `UserId` to begin with `id: `, \ - found: {}", - input, + found: {input}", ) }) }) @@ -166,6 +167,7 @@ impl UserId { ### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes ```rust +# extern crate juniper; # use juniper::{ # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # ScalarValue, ScalarToken, Value @@ -190,7 +192,7 @@ where S: ScalarValue { match v { - StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::String(s) => Value::scalar(s.to_owned()), StringOrInt::Int(i) => Value::scalar(*i), } } @@ -200,15 +202,12 @@ where S: ScalarValue { v.as_string_value() - .map(|s| StringOrInt::String(s.to_owned())) + .map(|s| StringOrInt::String(s.into())) .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } -fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> -where - S: ScalarValue -{ +fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) .or_else(|_| >::from_str(value)) } @@ -228,6 +227,7 @@ Path can be simply `with = Self` (default path where macro expects resolvers to in case there is an impl block with custom resolvers: ```rust +# extern crate juniper; # use juniper::{ # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # ScalarValue, ScalarToken, Value @@ -243,7 +243,7 @@ enum StringOrInt { impl StringOrInt { fn to_output(&self) -> Value { match self { - Self::String(str) => Value::scalar(str.to_owned()), + Self::String(s) => Value::scalar(s.to_owned()), Self::Int(i) => Value::scalar(*i), } } @@ -253,12 +253,12 @@ impl StringOrInt { S: ScalarValue, { v.as_string_value() - .map(|s| Self::String(s.to_owned())) + .map(|s| Self::String(s.into())) .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult where S: ScalarValue, { @@ -273,6 +273,7 @@ impl StringOrInt { Or it can be path to a module, where custom resolvers are located. ```rust +# extern crate juniper; # use juniper::{ # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # ScalarValue, ScalarToken, Value @@ -293,7 +294,7 @@ mod string_or_int { S: ScalarValue, { match v { - StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::String(s) => Value::scalar(s.to_owned()), StringOrInt::Int(i) => Value::scalar(*i), } } @@ -303,12 +304,12 @@ mod string_or_int { S: ScalarValue, { v.as_string_value() - .map(|s| StringOrInt::String(s.to_owned())) + .map(|s| StringOrInt::String(s.into())) .or_else(|| v.as_int_value().map(StringOrInt::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult where S: ScalarValue, { @@ -323,6 +324,7 @@ mod string_or_int { Also, you can partially override `#[graphql(with)]` attribute with other custom scalars. ```rust +# extern crate juniper; # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; # #[derive(GraphQLScalar)] @@ -338,7 +340,7 @@ impl StringOrInt { S: ScalarValue, { match self { - Self::String(str) => Value::scalar(str.to_owned()), + Self::String(s) => Value::scalar(s.to_owned()), Self::Int(i) => Value::scalar(*i), } } @@ -348,9 +350,9 @@ impl StringOrInt { S: ScalarValue, { v.as_string_value() - .map(|s| Self::String(s.to_owned())) + .map(|s| Self::String(s.into())) .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } } # @@ -403,8 +405,8 @@ mod date_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) + .ok_or_else(|| format!("Expected `String`, found: {v}")) + .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) } } # diff --git a/book/src/types/unions.md b/book/src/types/unions.md index ac5d7db2..bfce7f77 100644 --- a/book/src/types/unions.md +++ b/book/src/types/unions.md @@ -130,7 +130,7 @@ impl Character { # fn main() {} ``` -With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163). +With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/October2021#example-f8163). ```rust # #![allow(dead_code)] @@ -485,7 +485,7 @@ enum Character { -[1]: https://spec.graphql.org/June2018/#sec-Unions +[1]: https://spec.graphql.org/October2021#sec-Unions [2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html -[5]: https://spec.graphql.org/June2018/#sec-Interfaces +[5]: https://spec.graphql.org/October2021#sec-Interfaces [6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html diff --git a/book/tests/Cargo.toml b/book/tests/Cargo.toml deleted file mode 100644 index f73d6fb9..00000000 --- a/book/tests/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "juniper_book_tests" -version = "0.0.0" -edition = "2018" -authors = ["Magnus Hallin "] -publish = false - -[dependencies] -derive_more = "0.99" -futures = "0.3" -iron = "0.6" -juniper = { path = "../../juniper" } -juniper_iron = { path = "../../juniper_iron" } -juniper_subscriptions = { path = "../../juniper_subscriptions" } -mount = "0.4" -serde_json = "1.0" -skeptic = "0.13" -tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "sync"] } -uuid = "1.0" - -[build-dependencies] -skeptic = "0.13" diff --git a/book/tests/build.rs b/book/tests/build.rs deleted file mode 100644 index 80e0cbe1..00000000 --- a/book/tests/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - let files = skeptic::markdown_files_of_directory("../src/"); - skeptic::generate_doc_tests(&files); -} diff --git a/book/tests/src/lib.rs b/book/tests/src/lib.rs deleted file mode 100644 index e1d5434b..00000000 --- a/book/tests/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![deny(warnings)] - -include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs")); diff --git a/examples/actix_subscriptions/Cargo.toml b/examples/actix_subscriptions/Cargo.toml index 9bab7b3b..9c200447 100644 --- a/examples/actix_subscriptions/Cargo.toml +++ b/examples/actix_subscriptions/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "example_actix_subscriptions" version = "0.0.0" -edition = "2018" +edition = "2021" +rust-version = "1.62" authors = ["Mihai Dinculescu "] publish = false diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 66480242..6747105f 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -9,9 +9,9 @@ use actix_web::{ }; use juniper::{ - graphql_object, graphql_subscription, graphql_value, + graphql_subscription, graphql_value, tests::fixtures::starwars::schema::{Database, Query}, - EmptyMutation, FieldError, RootNode, + EmptyMutation, FieldError, GraphQLObject, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; use juniper_graphql_ws::ConnectionConfig; @@ -37,23 +37,12 @@ async fn graphql( struct Subscription; +#[derive(GraphQLObject)] struct RandomHuman { id: String, name: String, } -// TODO: remove this when async interfaces are merged -#[graphql_object(context = Database)] -impl RandomHuman { - fn id(&self) -> &str { - &self.id - } - - fn name(&self) -> &str { - &self.name - } -} - type RandomHumanStream = Pin> + Send>>; @@ -84,8 +73,8 @@ impl Subscription { let human = context.get_human(&random_id).unwrap().clone(); yield Ok(RandomHuman { - id: human.id().to_owned(), - name: human.name().unwrap().to_owned(), + id: human.id().into(), + name: human.name().unwrap().into(), }) } } @@ -142,7 +131,7 @@ async fn main() -> std::io::Result<()> { .finish() })) }) - .bind(format!("{}:{}", "127.0.0.1", 8080))? + .bind("127.0.0.1:8080")? .run() .await } diff --git a/examples/basic_subscriptions/Cargo.toml b/examples/basic_subscriptions/Cargo.toml index f03bdef4..252bdbeb 100644 --- a/examples/basic_subscriptions/Cargo.toml +++ b/examples/basic_subscriptions/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "example_basic_subscriptions" version = "0.0.0" -edition = "2018" +edition = "2021" +rust-version = "1.62" authors = ["Jordao Rosario "] publish = false diff --git a/examples/basic_subscriptions/src/main.rs b/examples/basic_subscriptions/src/main.rs index 82d4b46f..df523674 100644 --- a/examples/basic_subscriptions/src/main.rs +++ b/examples/basic_subscriptions/src/main.rs @@ -16,7 +16,7 @@ impl juniper::Context for Database {} impl Database { fn new() -> Self { - Self {} + Self } } @@ -45,7 +45,7 @@ impl Subscription { type Schema = RootNode<'static, Query, EmptyMutation, Subscription>; fn schema() -> Schema { - Schema::new(Query {}, EmptyMutation::new(), Subscription {}) + Schema::new(Query, EmptyMutation::new(), Subscription) } #[tokio::main] diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml index 94208317..0f891304 100644 --- a/examples/warp_async/Cargo.toml +++ b/examples/warp_async/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "example_warp_async" version = "0.0.0" -edition = "2018" +edition = "2021" +rust-version = "1.62" authors = ["Christoph Herzog "] publish = false diff --git a/examples/warp_subscriptions/Cargo.toml b/examples/warp_subscriptions/Cargo.toml index 6e22c5a7..5cdc12d6 100644 --- a/examples/warp_subscriptions/Cargo.toml +++ b/examples/warp_subscriptions/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "example_warp_subscriptions" version = "0.0.0" -edition = "2018" +edition = "2021" +rust-version = "1.62" publish = false [dependencies] async-stream = "0.3" env_logger = "0.9" -futures = "0.3.1" +futures = "0.3" juniper = { path = "../../juniper" } juniper_graphql_ws = { path = "../../juniper_graphql_ws" } juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] } diff --git a/examples/warp_subscriptions/src/main.rs b/examples/warp_subscriptions/src/main.rs index abac06a5..ded89062 100644 --- a/examples/warp_subscriptions/src/main.rs +++ b/examples/warp_subscriptions/src/main.rs @@ -12,7 +12,7 @@ use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws}; use warp::{http::Response, Filter}; #[derive(Clone)] -struct Context {} +struct Context; impl juniper::Context for Context {} @@ -46,7 +46,7 @@ impl User { async fn friends(&self) -> Vec { if self.id == 1 { - return vec![ + vec![ User { id: 11, kind: UserKind::User, @@ -62,15 +62,15 @@ impl User { kind: UserKind::Guest, name: "user13".into(), }, - ]; + ] } else if self.id == 2 { - return vec![User { + vec![User { id: 21, kind: UserKind::User, name: "user21".into(), - }]; + }] } else if self.id == 3 { - return vec![ + vec![ User { id: 31, kind: UserKind::User, @@ -81,9 +81,9 @@ impl User { kind: UserKind::Guest, name: "user32".into(), }, - ]; + ] } else { - return vec![]; + vec![] } } } @@ -123,7 +123,7 @@ impl Subscription { yield Ok(User { id: counter, kind: UserKind::Admin, - name: "stream user".to_string(), + name: "stream user".into(), }) } } @@ -149,11 +149,11 @@ async fn main() { let homepage = warp::path::end().map(|| { Response::builder() .header("content-type", "text/html") - .body("

juniper_subscriptions demo

visit graphql playground".to_string()) + .body("

juniper_subscriptions demo

visit graphql playground") }); let qm_schema = schema(); - let qm_state = warp::any().map(move || Context {}); + let qm_state = warp::any().map(|| Context); let qm_graphql_filter = juniper_warp::make_graphql_filter(qm_schema, qm_state.boxed()); let root_node = Arc::new(schema()); @@ -165,10 +165,10 @@ async fn main() { .map(move |ws: warp::ws::Ws| { let root_node = root_node.clone(); ws.on_upgrade(move |websocket| async move { - serve_graphql_ws(websocket, root_node, ConnectionConfig::new(Context {})) + serve_graphql_ws(websocket, root_node, ConnectionConfig::new(Context)) .map(|r| { if let Err(e) = r { - println!("Websocket error: {}", e); + println!("Websocket error: {e}"); } }) .await diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 1bff5bca..bab5569f 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -27,10 +27,11 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Removed support for `dyn` attribute argument (interface values as trait objects). - Removed support for `downcast` attribute argument (custom resolution into implementer types). - Removed support for `async` trait methods (not required anymore). - - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). + - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). ([#113]) - Forbade default implementations of non-ignored trait methods. - Supported coercion of additional `null`able arguments and return sub-typing on implementer. - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) + - Supported interfaces implementing other interfaces. ([#1028]) - Split `#[derive(GraphQLScalarValue)]` macro into: - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) - Supported generic `ScalarValue`. @@ -47,6 +48,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010]) - Disabled `chrono` [Cargo feature] by default. - Removed `scalar-naivetime` [Cargo feature]. +- Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#1081], [#528]) ### Added @@ -70,8 +72,13 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Unsupported expressions in `graphql_value!` macro. ([#996], [#503]) - Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004]) - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) +- Incorrect input value coercion with defaults. ([#1080], [#1073]) +- Incorrect error when explicit `null` provided for `null`able list input parameter. ([#1086], [#1085]) +- Stack overflow on nested GraphQL fragments. ([CVE-2022-31173]) +[#113]: /../../issues/113 [#503]: /../../issues/503 +[#528]: /../../issues/528 [#750]: /../../issues/750 [#798]: /../../issues/798 [#918]: /../../issues/918 @@ -94,11 +101,18 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1017]: /../../pull/1017 [#1025]: /../../pull/1025 [#1026]: /../../pull/1026 +[#1028]: /../../pull/1028 [#1051]: /../../issues/1051 [#1054]: /../../pull/1054 [#1057]: /../../pull/1057 [#1060]: /../../pull/1060 +[#1073]: /../../issues/1073 +[#1080]: /../../pull/1080 +[#1081]: /../../pull/1081 +[#1085]: /../../issues/1085 +[#1086]: /../../pull/1086 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 +[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 5c5fbc70..bc0e030d 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper" version = "0.16.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "GraphQL server library." license = "BSD-2-Clause" authors = [ @@ -38,11 +39,11 @@ schema-language = ["graphql-parser"] anyhow = { version = "1.0.32", default-features = false, optional = true } async-trait = "0.1.39" bigdecimal = { version = "0.3", optional = true } -bson = { version = "2.0", features = ["chrono-0_4"], optional = true } -chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true } +bson = { version = "2.4", features = ["chrono-0_4"], optional = true } +chrono = { version = "0.4.20", features = ["alloc"], default-features = false, optional = true } chrono-tz = { version = "0.6", default-features = false, optional = true } fnv = "1.0.3" -futures = { version = "0.3.1", features = ["alloc"], default-features = false } +futures = { version = "0.3.22", features = ["alloc"], default-features = false } futures-enum = { version = "0.1.12", default-features = false } graphql-parser = { version = "0.4", optional = true } indexmap = { version = "1.0", features = ["serde-1"] } @@ -58,12 +59,10 @@ uuid = { version = "1.0", default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } -# not used, to fix `bson` compilation only -uuid_08 = { version = "0.8", package = "uuid", default-features = false, features = ["wasm-bindgen"] } [dev-dependencies] bencher = "0.1.2" -chrono = { version = "0.4", features = ["alloc"], default-features = false } +chrono = { version = "0.4.20", features = ["alloc"], default-features = false } pretty_assertions = "1.0.0" serde_json = "1.0.2" tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] } diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index ea2c0fec..68eb1be4 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -114,7 +114,7 @@ pub struct Directive<'a, S> { } #[allow(missing_docs)] -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum OperationType { Query, Mutation, @@ -224,10 +224,10 @@ impl<'a> Type<'a> { impl<'a> fmt::Display for Type<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Named(n) => write!(f, "{}", n), - Self::NonNullNamed(n) => write!(f, "{}!", n), - Self::List(t, _) => write!(f, "[{}]", t), - Self::NonNullList(t, _) => write!(f, "[{}]!", t), + Self::Named(n) => write!(f, "{n}"), + Self::NonNullNamed(n) => write!(f, "{n}!"), + Self::List(t, _) => write!(f, "[{t}]"), + Self::NonNullList(t, _) => write!(f, "[{t}]!"), } } } @@ -248,12 +248,12 @@ impl InputValue { /// Construct an enum value. pub fn enum_value>(s: T) -> Self { - Self::Enum(s.as_ref().to_owned()) + Self::Enum(s.as_ref().into()) } /// Construct a variable value. pub fn variable>(v: T) -> Self { - Self::Variable(v.as_ref().to_owned()) + Self::Variable(v.as_ref().into()) } /// Construct a [`Spanning::unlocated`] list. @@ -282,7 +282,7 @@ impl InputValue { o.into_iter() .map(|(k, v)| { ( - Spanning::unlocated(k.as_ref().to_owned()), + Spanning::unlocated(k.as_ref().into()), Spanning::unlocated(v), ) }) @@ -295,25 +295,36 @@ impl InputValue { Self::Object(o) } - /// Resolve all variables to their values. + /// Resolves all variables of this [`InputValue`] to their actual `values`. + /// + /// If a variable is not present in the `values`: + /// - Returns [`None`] in case this is an [`InputValue::Variable`]. + /// - Skips field in case of an [`InputValue::Object`] field. + /// - Replaces with an [`InputValue::Null`] in case of an + /// [`InputValue::List`] element. + /// + /// This is done, because for an [`InputValue::Variable`] (or an + /// [`InputValue::Object`] field) a default value can be used later, if it's + /// provided. While on contrary, a single [`InputValue::List`] element + /// cannot have a default value. #[must_use] - pub fn into_const(self, vars: &Variables) -> Self + pub fn into_const(self, values: &Variables) -> Option where S: Clone, { match self { - Self::Variable(v) => vars.get(&v).map_or_else(InputValue::null, Clone::clone), - Self::List(l) => Self::List( + Self::Variable(v) => values.get(&v).cloned(), + Self::List(l) => Some(Self::List( l.into_iter() - .map(|s| s.map(|v| v.into_const(vars))) + .map(|s| s.map(|v| v.into_const(values).unwrap_or_else(Self::null))) .collect(), - ), - Self::Object(o) => Self::Object( + )), + Self::Object(o) => Some(Self::Object( o.into_iter() - .map(|(sk, sv)| (sk, sv.map(|v| v.into_const(vars)))) + .filter_map(|(sk, sv)| sv.and_then(|v| v.into_const(values)).map(|sv| (sk, sv))) .collect(), - ), - v => v, + )), + v => Some(v), } } @@ -456,13 +467,13 @@ impl fmt::Display for InputValue { Self::Null => write!(f, "null"), Self::Scalar(s) => { if let Some(s) = s.as_str() { - write!(f, "\"{}\"", s) + write!(f, "\"{s}\"") } else { - write!(f, "{}", s) + write!(f, "{s}") } } - Self::Enum(v) => write!(f, "{}", v), - Self::Variable(v) => write!(f, "${}", v), + Self::Enum(v) => write!(f, "{v}"), + Self::Variable(v) => write!(f, "${v}"), Self::List(v) => { write!(f, "[")?; for (i, spanning) in v.iter().enumerate() { @@ -577,30 +588,30 @@ mod tests { #[test] fn test_input_value_fmt() { let value: InputValue = graphql_input_value!(null); - assert_eq!(format!("{}", value), "null"); + assert_eq!(value.to_string(), "null"); let value: InputValue = graphql_input_value!(123); - assert_eq!(format!("{}", value), "123"); + assert_eq!(value.to_string(), "123"); let value: InputValue = graphql_input_value!(12.3); - assert_eq!(format!("{}", value), "12.3"); + assert_eq!(value.to_string(), "12.3"); let value: InputValue = graphql_input_value!("FOO"); - assert_eq!(format!("{}", value), "\"FOO\""); + assert_eq!(value.to_string(), "\"FOO\""); let value: InputValue = graphql_input_value!(true); - assert_eq!(format!("{}", value), "true"); + assert_eq!(value.to_string(), "true"); let value: InputValue = graphql_input_value!(BAR); - assert_eq!(format!("{}", value), "BAR"); + assert_eq!(value.to_string(), "BAR"); let value: InputValue = graphql_input_value!(@baz); - assert_eq!(format!("{}", value), "$baz"); + assert_eq!(value.to_string(), "$baz"); let value: InputValue = graphql_input_value!([1, 2]); - assert_eq!(format!("{}", value), "[1, 2]"); + assert_eq!(value.to_string(), "[1, 2]"); let value: InputValue = graphql_input_value!({"foo": 1,"bar": 2}); - assert_eq!(format!("{}", value), "{foo: 1, bar: 2}"); + assert_eq!(value.to_string(), "{foo: 1, bar: 2}"); } } diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 41bae7ee..20437082 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -10,7 +10,7 @@ use super::Variables; /// An enum that describes if a field is available in all types of the interface /// or only in a certain subtype -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Applies<'a> { /// The field is available independent from the type All, @@ -112,12 +112,11 @@ pub struct LookAheadSelection<'a, S: 'a> { pub(super) children: Vec>, } -impl<'a, S> Default for LookAheadSelection<'a, S> -where - S: ScalarValue, -{ +// Implemented manually to omit redundant `S: Default` trait bound, imposed by +// `#[derive(Default)]`. +impl<'a, S: 'a> Default for LookAheadSelection<'a, S> { fn default() -> Self { - LookAheadSelection { + Self { name: "", alias: None, arguments: vec![], diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 47d5fead..9ab412d4 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -304,7 +304,7 @@ where type Type; #[doc(hidden)] - fn into(self, ctx: &'a C) -> FieldResult, S>; + fn into_resolvable(self, ctx: &'a C) -> FieldResult, S>; } impl<'a, S, T, C> IntoResolvable<'a, S, T, C> for T @@ -315,7 +315,7 @@ where { type Type = T; - fn into(self, ctx: &'a C) -> FieldResult, S> { + fn into_resolvable(self, ctx: &'a C) -> FieldResult, S> { Ok(Some((FromContext::from(ctx), self))) } } @@ -328,7 +328,7 @@ where { type Type = T; - fn into(self, ctx: &'a C) -> FieldResult, S> { + fn into_resolvable(self, ctx: &'a C) -> FieldResult, S> { self.map(|v: T| Some((>::from(ctx), v))) .map_err(IntoFieldError::into_field_error) } @@ -341,7 +341,7 @@ where { type Type = T; - fn into(self, _: &'a C) -> FieldResult, S> { + fn into_resolvable(self, _: &'a C) -> FieldResult, S> { Ok(Some(self)) } } @@ -354,7 +354,7 @@ where type Type = T; #[allow(clippy::type_complexity)] - fn into(self, _: &'a C) -> FieldResult)>, S> { + fn into_resolvable(self, _: &'a C) -> FieldResult)>, S> { Ok(self.map(|(ctx, v)| (ctx, Some(v)))) } } @@ -367,7 +367,7 @@ where { type Type = T; - fn into(self, _: &'a C) -> FieldResult, S2> { + fn into_resolvable(self, _: &'a C) -> FieldResult, S2> { self.map(Some).map_err(FieldError::map_scalar_value) } } @@ -382,7 +382,7 @@ where type Type = T; #[allow(clippy::type_complexity)] - fn into(self, _: &'a C) -> FieldResult)>, S2> { + fn into_resolvable(self, _: &'a C) -> FieldResult)>, S2> { self.map(|o| o.map(|(ctx, v)| (ctx, Some(v)))) .map_err(FieldError::map_scalar_value) } @@ -774,7 +774,7 @@ impl<'a> FieldPath<'a> { FieldPath::Root(_) => (), FieldPath::Field(name, _, parent) => { parent.construct_path(acc); - acc.push((*name).to_owned()); + acc.push((*name).into()); } } } @@ -791,7 +791,7 @@ impl ExecutionError { pub fn new(location: SourcePosition, path: &[&str], error: FieldError) -> ExecutionError { ExecutionError { location, - path: path.iter().map(|s| (*s).to_owned()).collect(), + path: path.iter().map(|s| (*s).into()).collect(), error, } } @@ -814,13 +814,13 @@ impl ExecutionError { /// Create new `Executor` and start query/mutation execution. /// Returns `IsSubscription` error if subscription is passed. -pub fn execute_validated_query<'a, 'b, QueryT, MutationT, SubscriptionT, S>( +pub fn execute_validated_query<'b, QueryT, MutationT, SubscriptionT, S>( document: &'b Document, operation: &'b Spanning>, root_node: &RootNode, variables: &Variables, context: &QueryT::Context, -) -> Result<(Value, Vec>), GraphQLError<'a>> +) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, @@ -842,10 +842,10 @@ where defs.item .items .iter() - .filter_map(|&(ref name, ref def)| { + .filter_map(|(name, def)| { def.default_value .as_ref() - .map(|i| (name.item.to_owned(), i.item.clone())) + .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); @@ -914,7 +914,7 @@ pub async fn execute_validated_query_async<'a, 'b, QueryT, MutationT, Subscripti root_node: &RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &QueryT::Context, -) -> Result<(Value, Vec>), GraphQLError<'a>> +) -> Result<(Value, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -943,7 +943,7 @@ where .filter_map(|&(ref name, ref def)| { def.default_value .as_ref() - .map(|i| (name.item.to_owned(), i.item.clone())) + .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); @@ -1011,10 +1011,10 @@ where } #[doc(hidden)] -pub fn get_operation<'b, 'd, 'e, S>( +pub fn get_operation<'b, 'd, S>( document: &'b Document<'d, S>, operation_name: Option<&str>, -) -> Result<&'b Spanning>, GraphQLError<'e>> +) -> Result<&'b Spanning>, GraphQLError> where S: ScalarValue, { @@ -1058,7 +1058,7 @@ pub async fn resolve_validated_subscription< root_node: &'r RootNode<'r, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &'r QueryT::Context, -) -> Result<(Value>, Vec>), GraphQLError<'r>> +) -> Result<(Value>, Vec>), GraphQLError> where 'r: 'exec_ref, 'd: 'r, @@ -1090,7 +1090,7 @@ where .filter_map(|&(ref name, ref def)| { def.default_value .as_ref() - .map(|i| (name.item.to_owned(), i.item.clone())) + .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); @@ -1172,7 +1172,7 @@ impl<'r, S: 'r> Registry<'r, S> { if !self.types.contains_key(name) { self.insert_placeholder( validated_name.clone(), - Type::NonNullNamed(Cow::Owned(name.to_string())), + Type::NonNullNamed(Cow::Owned(name.into())), ); let meta = T::meta(info, self); self.types.insert(validated_name, meta); @@ -1209,7 +1209,7 @@ impl<'r, S: 'r> Registry<'r, S> { S: ScalarValue, { Field { - name: smartstring::SmartString::from(name), + name: name.into(), description: None, arguments: None, field_type: self.get_type::(info), @@ -1227,9 +1227,6 @@ impl<'r, S: 'r> Registry<'r, S> { } /// Creates an [`Argument`] with the provided default `value`. - /// - /// When called with type `T`, the actual [`Argument`] will be given the - /// type `Option`. pub fn arg_with_default( &mut self, name: &str, @@ -1240,7 +1237,7 @@ impl<'r, S: 'r> Registry<'r, S> { T: GraphQLType + ToInputValue + FromInputValue, S: ScalarValue, { - Argument::new(name, self.get_type::>(info)).default_value(value.to_input_value()) + Argument::new(name, self.get_type::(info)).default_value(value.to_input_value()) } fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) { @@ -1258,7 +1255,7 @@ impl<'r, S: 'r> Registry<'r, S> { { let name = T::name(info).expect("Scalar types must be named. Implement `name()`"); - ScalarMeta::new::(Cow::Owned(name.to_string())) + ScalarMeta::new::(Cow::Owned(name.into())) } /// Creates a [`ListMeta`] type. @@ -1302,7 +1299,7 @@ impl<'r, S: 'r> Registry<'r, S> { let mut v = fields.to_vec(); v.push(self.field::("__typename", &())); - ObjectMeta::new(Cow::Owned(name.to_string()), &v) + ObjectMeta::new(Cow::Owned(name.into()), &v) } /// Creates an [`EnumMeta`] type out of the provided `values`. @@ -1318,7 +1315,7 @@ impl<'r, S: 'r> Registry<'r, S> { { let name = T::name(info).expect("Enum types must be named. Implement `name()`"); - EnumMeta::new::(Cow::Owned(name.to_string()), values) + EnumMeta::new::(Cow::Owned(name.into()), values) } /// Creates an [`InterfaceMeta`] type with the given `fields`. @@ -1335,7 +1332,7 @@ impl<'r, S: 'r> Registry<'r, S> { let mut v = fields.to_vec(); v.push(self.field::("__typename", &())); - InterfaceMeta::new(Cow::Owned(name.to_string()), &v) + InterfaceMeta::new(Cow::Owned(name.into()), &v) } /// Creates an [`UnionMeta`] type of the given `types`. @@ -1346,7 +1343,7 @@ impl<'r, S: 'r> Registry<'r, S> { { let name = T::name(info).expect("Union types must be named. Implement name()"); - UnionMeta::new(Cow::Owned(name.to_string()), types) + UnionMeta::new(Cow::Owned(name.into()), types) } /// Creates an [`InputObjectMeta`] type with the given `args`. @@ -1362,6 +1359,6 @@ impl<'r, S: 'r> Registry<'r, S> { { let name = T::name(info).expect("Input object types must be named. Implement name()"); - InputObjectMeta::new::(Cow::Owned(name.to_string()), args) + InputObjectMeta::new::(Cow::Owned(name.into()), args) } } diff --git a/juniper/src/executor_tests/async_await/mod.rs b/juniper/src/executor_tests/async_await/mod.rs index 277c0197..228c53d3 100644 --- a/juniper/src/executor_tests/async_await/mod.rs +++ b/juniper/src/executor_tests/async_await/mod.rs @@ -1,5 +1,6 @@ use crate::{ - graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLEnum, RootNode, Value, + graphql_object, graphql_value, graphql_vars, EmptyMutation, EmptySubscription, GraphQLEnum, + RootNode, Value, }; #[derive(GraphQLEnum)] @@ -30,7 +31,7 @@ impl User { (0..10) .map(|index| User { id: index, - name: format!("user{}", index), + name: format!("user{index}"), kind: UserKind::User, }) .collect() @@ -55,7 +56,7 @@ impl Query { } async fn field_async_plain() -> String { - "field_async_plain".to_string() + "field_async_plain".into() } fn user(id: String) -> User { @@ -86,8 +87,7 @@ async fn async_simple() { } "#; - let vars = Default::default(); - let (res, errs) = crate::execute(doc, None, &schema, &vars, &()) + let (res, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .unwrap(); diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs index adc05594..1f902e69 100644 --- a/juniper/src/executor_tests/directives.rs +++ b/juniper/src/executor_tests/directives.rs @@ -35,7 +35,7 @@ where assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let obj = result.as_object_value().expect("Result is not an object"); diff --git a/juniper/src/executor_tests/enums.rs b/juniper/src/executor_tests/enums.rs index 69cda71d..11fa80ae 100644 --- a/juniper/src/executor_tests/enums.rs +++ b/juniper/src/executor_tests/enums.rs @@ -22,7 +22,7 @@ struct TestType; #[crate::graphql_object] impl TestType { fn to_string(color: Color) -> String { - format!("Color::{:?}", color) + format!("Color::{color:?}") } fn a_color() -> Color { @@ -46,7 +46,7 @@ where assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let obj = result.as_object_value().expect("Result is not an object"); diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index a1da0673..06d93fc0 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -96,7 +96,7 @@ mod field_execution { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -180,7 +180,7 @@ mod merge_parallel_fragments { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -288,7 +288,7 @@ mod merge_parallel_inline_fragments { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -355,7 +355,7 @@ mod threads_context_correctly { &schema, &vars, &TestContext { - value: "Context value".to_owned(), + value: "Context value".into(), }, ) .await @@ -363,7 +363,7 @@ mod threads_context_correctly { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"a": "Context value"})); } @@ -410,7 +410,7 @@ mod dynamic_context_switching { let res = context .items .get(&key) - .ok_or(format!("Could not find key {}", key)) + .ok_or(format!("Could not find key {key}")) .map(|c| (c, ItemRef))?; Ok(res) } @@ -420,7 +420,7 @@ mod dynamic_context_switching { key: i32, ) -> FieldResult> { if key > 100 { - Err(format!("Key too large: {}", key))?; + Err(format!("Key too large: {key}"))?; } Ok(context.items.get(&key).map(|c| (c, ItemRef))) } @@ -452,13 +452,13 @@ mod dynamic_context_switching { ( 0, InnerContext { - value: "First value".to_owned(), + value: "First value".into(), }, ), ( 1, InnerContext { - value: "Second value".to_owned(), + value: "Second value".into(), }, ), ] @@ -472,7 +472,7 @@ mod dynamic_context_switching { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -500,13 +500,13 @@ mod dynamic_context_switching { ( 0, InnerContext { - value: "First value".to_owned(), + value: "First value".into(), }, ), ( 1, InnerContext { - value: "Second value".to_owned(), + value: "Second value".into(), }, ), ] @@ -520,7 +520,7 @@ mod dynamic_context_switching { assert_eq!(errs, vec![]); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } @@ -542,13 +542,13 @@ mod dynamic_context_switching { ( 0, InnerContext { - value: "First value".to_owned(), + value: "First value".into(), }, ), ( 1, InnerContext { - value: "Second value".to_owned(), + value: "Second value".into(), }, ), ] @@ -569,7 +569,7 @@ mod dynamic_context_switching { )], ); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); } @@ -593,13 +593,13 @@ mod dynamic_context_switching { ( 0, InnerContext { - value: "First value".to_owned(), + value: "First value".into(), }, ), ( 1, InnerContext { - value: "Second value".to_owned(), + value: "Second value".into(), }, ), ] @@ -620,7 +620,7 @@ mod dynamic_context_switching { )], ); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -647,13 +647,13 @@ mod dynamic_context_switching { ( 0, InnerContext { - value: "First value".to_owned(), + value: "First value".into(), }, ), ( 1, InnerContext { - value: "Second value".to_owned(), + value: "Second value".into(), }, ), ] @@ -667,7 +667,7 @@ mod dynamic_context_switching { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } @@ -752,7 +752,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -783,7 +783,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); @@ -811,7 +811,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); @@ -839,7 +839,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"inner": {"nullableField": null}}),); @@ -867,7 +867,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); @@ -895,7 +895,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -926,7 +926,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); @@ -954,7 +954,7 @@ mod propagates_errors_to_nullable_fields { .await .expect("Execution failed"); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 1044baaf..10d5e088 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -42,12 +42,12 @@ mod interface { Schema { pets: vec![ Dog { - name: "Odie".to_owned(), + name: "Odie".into(), woofs: true, } .into(), Cat { - name: "Garfield".to_owned(), + name: "Garfield".into(), meows: false, } .into(), @@ -77,7 +77,7 @@ mod interface { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -170,11 +170,11 @@ mod union { Schema { pets: vec![ Box::new(Dog { - name: "Odie".to_owned(), + name: "Odie".into(), woofs: true, }), Box::new(Cat { - name: "Garfield".to_owned(), + name: "Garfield".into(), meows: false, }), ], @@ -205,7 +205,7 @@ mod union { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, diff --git a/juniper/src/executor_tests/introspection/enums.rs b/juniper/src/executor_tests/introspection/enums.rs index 127f45ab..c70a60e9 100644 --- a/juniper/src/executor_tests/introspection/enums.rs +++ b/juniper/src/executor_tests/introspection/enums.rs @@ -92,7 +92,7 @@ where F: Fn((&Object, &Vec>)) -> (), { let schema = RootNode::new( - Root {}, + Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); @@ -103,7 +103,7 @@ where assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index f68b9796..2e70d088 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -76,9 +76,9 @@ struct FieldDescription { #[derive(GraphQLInputObject, Debug)] struct FieldWithDefaults { - #[graphql(default = "123")] + #[graphql(default = 123)] field_one: i32, - #[graphql(default = "456", description = "The second field")] + #[graphql(default = 456, description = "The second field")] field_two: i32, } @@ -117,7 +117,7 @@ where F: Fn(&Object, &Vec>) -> (), { let schema = RootNode::new( - Root {}, + Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); @@ -128,7 +128,7 @@ where assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() @@ -312,7 +312,7 @@ fn derive_derived() { format!( "{:?}", Derive { - field_one: "test".to_owned(), + field_one: "test".into(), }, ), "Derive { field_one: \"test\" }" @@ -462,6 +462,9 @@ async fn field_with_defaults_introspection() { name type { name + ofType { + name + } } defaultValue } @@ -477,12 +480,12 @@ async fn field_with_defaults_introspection() { assert_eq!(fields.len(), 2); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", - "type": {"name": "Int"}, + "type": {"name": null, "ofType": {"name": "Int"}}, "defaultValue": "123", }))); assert!(fields.contains(&graphql_value!({ "name": "fieldTwo", - "type": {"name": "Int"}, + "type": {"name": null, "ofType": {"name": "Int"}}, "defaultValue": "456", }))); }) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 25eff845..ee2501b2 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -69,7 +69,7 @@ async fn test_execution() { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); assert_eq!( result, @@ -114,7 +114,7 @@ async fn enum_introspection() { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() @@ -223,7 +223,7 @@ async fn interface_introspection() { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() @@ -247,7 +247,7 @@ async fn interface_introspection() { ); assert_eq!( type_info.get_field_value("interfaces"), - Some(&graphql_value!(null)), + Some(&graphql_value!([])), ); assert_eq!( type_info.get_field_value("enumValues"), @@ -355,7 +355,7 @@ async fn object_introspection() { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() @@ -406,7 +406,7 @@ async fn object_introspection() { assert_eq!(fields.len(), 2); - println!("Fields: {:#?}", fields); + println!("Fields: {fields:#?}"); assert!(fields.contains(&graphql_value!({ "name": "sampleEnum", @@ -444,9 +444,13 @@ async fn object_introspection() { "name": "second", "description": "The second number", "type": { - "name": "Int", - "kind": "SCALAR", - "ofType": null, + "name": null, + "kind": "NON_NULL", + "ofType": { + "name": "Int", + "kind": "SCALAR", + "ofType": null, + }, }, "defaultValue": "123", }], @@ -493,7 +497,7 @@ async fn scalar_introspection() { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); let type_info = result .as_object_value() diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 257f26c4..f49d4a23 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -23,7 +23,7 @@ impl TestComplexScalar { v.as_string_value() .filter(|s| *s == "SerializedValue") .map(|_| Self) - .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) + .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {v}"#)) } } @@ -49,7 +49,7 @@ struct ExampleInputObject { #[derive(GraphQLInputObject, Debug)] struct InputWithDefaults { - #[graphql(default = "123")] + #[graphql(default = 123)] a: i32, } @@ -58,41 +58,47 @@ struct TestType; #[graphql_object] impl TestType { fn field_with_object_input(input: Option) -> String { - format!("{:?}", input) + format!("{input:?}") } fn field_with_nullable_string_input(input: Option) -> String { - format!("{:?}", input) + format!("{input:?}") } fn field_with_non_nullable_string_input(input: String) -> String { - format!("{:?}", input) + format!("{input:?}") } fn field_with_default_argument_value( #[graphql(default = "Hello World")] input: String, ) -> String { - format!("{:?}", input) + format!("{input:?}") + } + + fn nullable_field_with_default_argument_value( + #[graphql(default = "Hello World".to_owned())] input: Option, + ) -> String { + format!("{input:?}") } fn field_with_nested_object_input(input: Option) -> String { - format!("{:?}", input) + format!("{input:?}") } fn list(input: Option>>) -> String { - format!("{:?}", input) + format!("{input:?}") } fn nn_list(input: Vec>) -> String { - format!("{:?}", input) + format!("{input:?}") } fn list_nn(input: Option>) -> String { - format!("{:?}", input) + format!("{input:?}") } fn nn_list_nn(input: Vec) -> String { - format!("{:?}", input) + format!("{input:?}") } fn example_input(arg: ExampleInputObject) -> String { @@ -104,11 +110,11 @@ impl TestType { } fn integer_input(value: i32) -> String { - format!("value: {}", value) + format!("value: {value}") } fn float_input(value: f64) -> String { - format!("value: {}", value) + format!("value: {value}") } } @@ -128,7 +134,7 @@ where assert_eq!(errs, []); - println!("Result: {:?}", result); + println!("Result: {result:?}"); let obj = result.as_object_value().expect("Result is not an object"); @@ -791,13 +797,14 @@ async fn default_argument_when_not_provided() { } #[tokio::test] -async fn default_argument_when_nullable_variable_not_provided() { - run_query( - r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, +async fn provided_variable_overwrites_default_value() { + run_variable_query( + r#"query q($input: String!) { fieldWithDefaultArgumentValue(input: $input) }"#, + graphql_vars! {"input": "Overwritten"}, |result| { assert_eq!( result.get_field_value("fieldWithDefaultArgumentValue"), - Some(&graphql_value!(r#""Hello World""#)), + Some(&graphql_value!(r#""Overwritten""#)), ); }, ) @@ -805,14 +812,28 @@ async fn default_argument_when_nullable_variable_not_provided() { } #[tokio::test] -async fn default_argument_when_nullable_variable_set_to_null() { +async fn default_argument_when_nullable_variable_not_provided() { + run_query( + r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#, + |result| { + assert_eq!( + result.get_field_value("nullableFieldWithDefaultArgumentValue"), + Some(&graphql_value!(r#"Some("Hello World")"#)), + ); + }, + ) + .await; +} + +#[tokio::test] +async fn null_when_nullable_variable_of_argument_with_default_value_set_to_null() { run_variable_query( - r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, + r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#, graphql_vars! {"input": null}, |result| { assert_eq!( - result.get_field_value("fieldWithDefaultArgumentValue"), - Some(&graphql_value!(r#""Hello World""#)), + result.get_field_value("nullableFieldWithDefaultArgumentValue"), + Some(&graphql_value!(r#"None"#)), ); }, ) diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs index 17420de3..45e310be 100644 --- a/juniper/src/http/mod.rs +++ b/juniper/src/http/mod.rs @@ -60,11 +60,8 @@ where self.variables .as_ref() .and_then(|iv| { - iv.to_object_value().map(|o| { - o.into_iter() - .map(|(k, v)| (k.to_owned(), v.clone())) - .collect() - }) + iv.to_object_value() + .map(|o| o.into_iter().map(|(k, v)| (k.into(), v.clone())).collect()) }) .unwrap_or_default() } @@ -86,11 +83,11 @@ where /// /// This is a simple wrapper around the `execute_sync` function exposed at the /// top level of this crate. - pub fn execute_sync<'a, QueryT, MutationT, SubscriptionT>( - &'a self, - root_node: &'a RootNode, + pub fn execute_sync( + &self, + root_node: &RootNode, context: &QueryT::Context, - ) -> GraphQLResponse<'a, S> + ) -> GraphQLResponse where S: ScalarValue, QueryT: GraphQLType, @@ -114,7 +111,7 @@ where &'a self, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'a QueryT::Context, - ) -> GraphQLResponse<'a, S> + ) -> GraphQLResponse where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -140,7 +137,7 @@ pub async fn resolve_into_stream<'req, 'rn, 'ctx, 'a, QueryT, MutationT, Subscri req: &'req GraphQLRequest, root_node: &'rn RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'ctx QueryT::Context, -) -> Result<(Value>, Vec>), GraphQLError<'a>> +) -> Result<(Value>, Vec>), GraphQLError> where 'req: 'a, 'rn: 'a, @@ -166,16 +163,16 @@ where /// to JSON and send it over the wire. Use the `is_ok` method to determine /// whether to send a 200 or 400 HTTP status code. #[derive(Debug)] -pub struct GraphQLResponse<'a, S = DefaultScalarValue>( - Result<(Value, Vec>), GraphQLError<'a>>, +pub struct GraphQLResponse( + Result<(Value, Vec>), GraphQLError>, ); -impl<'a, S> GraphQLResponse<'a, S> +impl GraphQLResponse where S: ScalarValue, { /// Constructs new `GraphQLResponse` using the given result - pub fn from_result(r: Result<(Value, Vec>), GraphQLError<'a>>) -> Self { + pub fn from_result(r: Result<(Value, Vec>), GraphQLError>) -> Self { Self(r) } @@ -193,12 +190,11 @@ where } } -impl<'a, T> Serialize for GraphQLResponse<'a, T> +impl Serialize for GraphQLResponse where T: Serialize + ScalarValue, Value: Serialize, ExecutionError: Serialize, - GraphQLError<'a>: Serialize, { fn serialize(&self, serializer: S) -> Result where @@ -272,7 +268,7 @@ where &'a self, root_node: &'a RootNode, context: &QueryT::Context, - ) -> GraphQLBatchResponse<'a, S> + ) -> GraphQLBatchResponse where QueryT: GraphQLType, MutationT: GraphQLType, @@ -298,7 +294,7 @@ where &'a self, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'a QueryT::Context, - ) -> GraphQLBatchResponse<'a, S> + ) -> GraphQLBatchResponse where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -340,20 +336,17 @@ where /// wheter to send a 200 or 400 HTTP status code. #[derive(Serialize)] #[serde(untagged)] -pub enum GraphQLBatchResponse<'a, S = DefaultScalarValue> +pub enum GraphQLBatchResponse where S: ScalarValue, { /// Result of a single operation in a GraphQL request. - Single(GraphQLResponse<'a, S>), + Single(GraphQLResponse), /// Result of a batch operation in a GraphQL request. - Batch(Vec>), + Batch(Vec>), } -impl<'a, S> GraphQLBatchResponse<'a, S> -where - S: ScalarValue, -{ +impl GraphQLBatchResponse { /// Returns if all the GraphQLResponse in this operation are ok, /// you can use it to determine wheter to send a 200 or 400 HTTP status code. pub fn is_ok(&self) -> bool { @@ -639,20 +632,20 @@ pub mod tests { "type":"connection_init", "payload":{} }"# - .to_owned(), + .into(), ), WsIntegrationMessage::Expect( r#"{ "type":"connection_ack" }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Expect( r#"{ "type":"ka" }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send( @@ -666,7 +659,7 @@ pub mod tests { "query":"subscription { asyncHuman { id, name, homePlanet } }" } }"# - .to_owned(), + .into(), ), WsIntegrationMessage::Expect( r#"{ @@ -682,7 +675,7 @@ pub mod tests { } } }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; @@ -692,7 +685,7 @@ pub mod tests { async fn test_ws_invalid_json(integration: &T) { let messages = vec![ - WsIntegrationMessage::Send("invalid json".to_owned()), + WsIntegrationMessage::Send("invalid json".into()), WsIntegrationMessage::Expect( r#"{ "type":"connection_error", @@ -700,7 +693,7 @@ pub mod tests { "message":"serde error: expected value at line 1 column 1" } }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; @@ -715,20 +708,20 @@ pub mod tests { "type":"connection_init", "payload":{} }"# - .to_owned(), + .into(), ), WsIntegrationMessage::Expect( r#"{ "type":"connection_ack" }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT ), WsIntegrationMessage::Expect( r#"{ "type":"ka" }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT ), WsIntegrationMessage::Send( @@ -742,7 +735,7 @@ pub mod tests { "query":"subscription { asyncHuman }" } }"# - .to_owned(), + .into(), ), WsIntegrationMessage::Expect( r#"{ @@ -756,7 +749,7 @@ pub mod tests { }] }] }"# - .to_owned(), + .into(), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT ) ]; diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index a1635fcc..3715536c 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -32,8 +32,6 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; type BigDecimal = bigdecimal::BigDecimal; mod bigdecimal_scalar { - use std::convert::TryFrom as _; - use super::*; pub(super) fn to_output(v: &BigDecimal) -> Value { @@ -45,13 +43,13 @@ mod bigdecimal_scalar { Ok(BigDecimal::from(i)) } else if let Some(f) = v.as_float_value() { BigDecimal::try_from(f) - .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {}", e)) + .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}")) } else { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { BigDecimal::from_str(s) - .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {}", e)) + .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {e}")) }) } } @@ -88,11 +86,10 @@ mod test { assert!( parsed.is_ok(), - "failed to parse `{:?}`: {:?}", - input, + "failed to parse `{input:?}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); + assert_eq!(parsed.unwrap(), expected, "input: {input:?}"); } } @@ -110,7 +107,7 @@ mod test { let input: InputValue = input; let parsed = BigDecimal::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -126,7 +123,7 @@ mod test { ] { let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); - assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); + assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } } } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index f22691b2..20c88fbe 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -14,9 +14,9 @@ mod object_id { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { - ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e)) + ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {e}")) }) } } @@ -28,15 +28,18 @@ mod utc_date_time { use super::*; pub(super) fn to_output(v: &UtcDateTime) -> Value { - Value::scalar((*v).to_rfc3339_string()) + Value::scalar( + (*v).try_to_rfc3339_string() + .unwrap_or_else(|e| panic!("failed to format `UtcDateTime` as RFC3339: {e}")), + ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { UtcDateTime::parse_rfc3339_str(s) - .map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `UtcDateTime`: {e}")) }) } } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index a0c6edc4..b1fb3a48 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -62,9 +62,9 @@ mod date { S: ScalarValue, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { - Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)) + Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}")) }) } } @@ -127,7 +127,7 @@ mod local_time { S: ScalarValue, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing @@ -135,7 +135,7 @@ mod local_time { LocalTime::parse_from_str(s, FORMAT_NO_MILLIS) .or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse_from_str(s, FORMAT)) - .map_err(|e| format!("Invalid `LocalTime`: {}", e)) + .map_err(|e| format!("Invalid `LocalTime`: {e}")) }) } } @@ -166,10 +166,10 @@ mod local_date_time { S: ScalarValue, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { LocalDateTime::parse_from_str(s, FORMAT) - .map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) + .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) }) } } @@ -219,10 +219,10 @@ mod date_time { Tz: TimeZone + FromFixedOffset, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::::parse_from_rfc3339(s) - .map_err(|e| format!("Invalid `DateTime`: {}", e)) + .map_err(|e| format!("Invalid `DateTime`: {e}")) .map(FromFixedOffset::from_fixed_offset) }) } @@ -345,11 +345,10 @@ mod date_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -372,7 +371,7 @@ mod date_test { let input: InputValue = input; let parsed = Date::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -394,7 +393,7 @@ mod date_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -420,11 +419,10 @@ mod local_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -451,7 +449,7 @@ mod local_time_test { let input: InputValue = input; let parsed = LocalTime::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -477,7 +475,7 @@ mod local_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -513,11 +511,10 @@ mod local_date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -546,7 +543,7 @@ mod local_date_time_test { let input: InputValue = input; let parsed = LocalDateTime::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -570,7 +567,7 @@ mod local_date_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -635,11 +632,10 @@ mod date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -673,7 +669,7 @@ mod date_time_test { let input: InputValue = input; let parsed = DateTime::::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -703,7 +699,7 @@ mod date_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index b6fa5732..8950f97f 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -34,10 +34,10 @@ mod tz { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { s.parse::() - .map_err(|e| format!("Failed to parse `TimeZone`: {}", e)) + .map_err(|e| format!("Failed to parse `TimeZone`: {e}")) }) } } diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index 2e74db32..52a6d20c 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -34,8 +34,6 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { - use std::convert::TryFrom as _; - use super::*; pub(super) fn to_output(v: &Decimal) -> Value { @@ -46,14 +44,13 @@ mod rust_decimal_scalar { if let Some(i) = v.as_int_value() { Ok(Decimal::from(i)) } else if let Some(f) = v.as_float_value() { - Decimal::try_from(f) - .map_err(|e| format!("Failed to parse `Decimal` from `Float`: {}", e)) + Decimal::try_from(f).map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}")) } else { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { Decimal::from_str(s) - .map_err(|e| format!("Failed to parse `Decimal` from `String`: {}", e)) + .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}")) }) } } @@ -84,11 +81,10 @@ mod test { assert!( parsed.is_ok(), - "failed to parse `{:?}`: {:?}", - input, + "failed to parse `{input:?}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); + assert_eq!(parsed.unwrap(), expected, "input: {input:?}"); } } @@ -108,7 +104,7 @@ mod test { let input: InputValue = input; let parsed = Decimal::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -117,7 +113,7 @@ mod test { for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); - assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); + assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } } } diff --git a/juniper/src/integrations/serde.rs b/juniper/src/integrations/serde.rs index 31a0b600..38779964 100644 --- a/juniper/src/integrations/serde.rs +++ b/juniper/src/integrations/serde.rs @@ -1,8 +1,4 @@ -use std::{ - convert::{TryFrom as _, TryInto as _}, - fmt, - marker::PhantomData, -}; +use std::{fmt, marker::PhantomData}; use indexmap::IndexMap; use serde::{ @@ -42,7 +38,7 @@ impl Serialize for ExecutionError { } } -impl<'a> Serialize for GraphQLError<'a> { +impl Serialize for GraphQLError { fn serialize(&self, ser: S) -> Result { #[derive(Serialize)] struct Helper { @@ -247,11 +243,11 @@ impl Serialize for SourcePosition { } } -impl<'a> Serialize for Spanning> { +impl Serialize for Spanning { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(2))?; - let msg = format!("{}", self.item); + let msg = self.item.to_string(); map.serialize_key("message")?; map.serialize_value(&msg)?; @@ -396,7 +392,7 @@ mod tests { #[test] fn error_extensions() { let mut obj: Object = Object::with_capacity(1); - obj.add_field("foo".to_string(), Value::scalar("bar")); + obj.add_field("foo", Value::scalar("bar")); assert_eq!( to_string(&ExecutionError::at_origin(FieldError::new( "foo error", diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index e05bf535..d2c88529 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -57,14 +57,14 @@ mod date { pub(super) fn to_output(v: &Date) -> Value { Value::scalar( v.format(FORMAT) - .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), + .unwrap_or_else(|e| panic!("Failed to format `Date`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) + .ok_or_else(|| format!("Expected `String`, found: {v}")) + .and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}"))) } } @@ -109,13 +109,13 @@ mod local_time { } else { v.format(FORMAT) } - .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)), + .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing @@ -123,7 +123,7 @@ mod local_time { LocalTime::parse(s, FORMAT_NO_MILLIS) .or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse(s, FORMAT)) - .map_err(|e| format!("Invalid `LocalTime`: {}", e)) + .map_err(|e| format!("Invalid `LocalTime`: {e}")) }) } } @@ -146,16 +146,15 @@ mod local_date_time { pub(super) fn to_output(v: &LocalDateTime) -> Value { Value::scalar( v.format(FORMAT) - .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)), + .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { - LocalDateTime::parse(s, FORMAT) - .map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) + LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}")) }) } } @@ -186,15 +185,15 @@ mod date_time { Value::scalar( v.to_offset(UtcOffset::UTC) .format(&Rfc3339) - .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)), + .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { - DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e)) + DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {e}")) }) .map(|dt| dt.to_offset(UtcOffset::UTC)) } @@ -228,16 +227,16 @@ mod utc_offset { pub(super) fn to_output(v: &UtcOffset) -> Value { Value::scalar( v.format(UTC_OFFSET_FORMAT) - .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)), + .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { UtcOffset::parse(s, UTC_OFFSET_FORMAT) - .map_err(|e| format!("Invalid `UtcOffset`: {}", e)) + .map_err(|e| format!("Invalid `UtcOffset`: {e}")) }) } } @@ -261,11 +260,10 @@ mod date_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -288,7 +286,7 @@ mod date_test { let input: InputValue = input; let parsed = Date::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -302,7 +300,7 @@ mod date_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -330,11 +328,10 @@ mod local_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -364,7 +361,7 @@ mod local_time_test { let input: InputValue = input; let parsed = LocalTime::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -378,7 +375,7 @@ mod local_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -402,11 +399,10 @@ mod local_date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -437,7 +433,7 @@ mod local_date_time_test { let input: InputValue = input; let parsed = LocalDateTime::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -455,7 +451,7 @@ mod local_date_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -490,11 +486,10 @@ mod date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -528,7 +523,7 @@ mod date_time_test { let input: InputValue = input; let parsed = DateTime::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -546,7 +541,7 @@ mod date_time_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } @@ -574,11 +569,10 @@ mod utc_offset_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {:?}", - raw, + "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); - assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } @@ -607,7 +601,7 @@ mod utc_offset_test { let input: InputValue = input; let parsed = UtcOffset::from_input_value(&input); - assert!(parsed.is_err(), "allows input: {:?}", input); + assert!(parsed.is_err(), "allows input: {input:?}"); } } @@ -620,7 +614,7 @@ mod utc_offset_test { ] { let actual: InputValue = val.to_input_value(); - assert_eq!(actual, expected, "on value: {}", val); + assert_eq!(actual, expected, "on value: {val}"); } } } diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index e3764c6b..6121ac56 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -14,8 +14,8 @@ mod url_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) + .ok_or_else(|| format!("Expected `String`, found: {v}")) + .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {e}"))) } } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 37f71042..4fed7f5f 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -16,8 +16,8 @@ mod uuid_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) + .ok_or_else(|| format!("Expected `String`, found: {v}")) + .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {e}"))) } } diff --git a/juniper/src/introspection/mod.rs b/juniper/src/introspection/mod.rs index aedd4ef9..e59a492e 100644 --- a/juniper/src/introspection/mod.rs +++ b/juniper/src/introspection/mod.rs @@ -5,15 +5,12 @@ pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str = /// The desired GraphQL introspection format for the canonical query /// () +#[derive(Clone, Copy, Debug, Default)] pub enum IntrospectionFormat { /// The canonical GraphQL introspection query. + #[default] All, + /// The canonical GraphQL introspection query without descriptions. WithoutDescriptions, } - -impl Default for IntrospectionFormat { - fn default() -> Self { - IntrospectionFormat::All - } -} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 0373175e..ac1e0329 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -6,6 +6,7 @@ // Required for using `juniper_codegen` macros inside this crate to resolve // absolute `::juniper` path correctly, without errors. +extern crate core; extern crate self as juniper; use std::fmt; @@ -93,10 +94,10 @@ pub use crate::{ }; /// An error that prevented query execution -#[derive(Debug, PartialEq)] #[allow(missing_docs)] -pub enum GraphQLError<'a> { - ParseError(Spanning>), +#[derive(Debug, Eq, PartialEq)] +pub enum GraphQLError { + ParseError(Spanning), ValidationError(Vec), NoOperationProvided, MultipleOperationsProvided, @@ -105,26 +106,38 @@ pub enum GraphQLError<'a> { NotSubscription, } -impl<'a> fmt::Display for GraphQLError<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl fmt::Display for GraphQLError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - GraphQLError::ParseError(error) => write!(f, "{}", error), - GraphQLError::ValidationError(errors) => { - for error in errors { - writeln!(f, "{}", error)?; + Self::ParseError(e) => write!(f, "{e}"), + Self::ValidationError(errs) => { + for e in errs { + writeln!(f, "{e}")?; } Ok(()) } - GraphQLError::NoOperationProvided => write!(f, "No operation provided"), - GraphQLError::MultipleOperationsProvided => write!(f, "Multiple operations provided"), - GraphQLError::UnknownOperationName => write!(f, "Unknown operation name"), - GraphQLError::IsSubscription => write!(f, "Operation is a subscription"), - GraphQLError::NotSubscription => write!(f, "Operation is not a subscription"), + Self::NoOperationProvided => write!(f, "No operation provided"), + Self::MultipleOperationsProvided => write!(f, "Multiple operations provided"), + Self::UnknownOperationName => write!(f, "Unknown operation name"), + Self::IsSubscription => write!(f, "Operation is a subscription"), + Self::NotSubscription => write!(f, "Operation is not a subscription"), } } } -impl<'a> std::error::Error for GraphQLError<'a> {} +impl std::error::Error for GraphQLError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ParseError(e) => Some(e), + Self::ValidationError(errs) => Some(errs.first()?), + Self::NoOperationProvided + | Self::MultipleOperationsProvided + | Self::UnknownOperationName + | Self::IsSubscription + | Self::NotSubscription => None, + } + } +} /// Execute a query synchronously in a provided schema pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>( @@ -133,7 +146,7 @@ pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>( root_node: &'a RootNode, variables: &Variables, context: &QueryT::Context, -) -> Result<(Value, Vec>), GraphQLError<'a>> +) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, @@ -172,7 +185,7 @@ pub async fn execute<'a, S, QueryT, MutationT, SubscriptionT>( root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &QueryT::Context, -) -> Result<(Value, Vec>), GraphQLError<'a>> +) -> Result<(Value, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -216,7 +229,7 @@ pub async fn resolve_into_stream<'a, S, QueryT, MutationT, SubscriptionT>( root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &'a QueryT::Context, -) -> Result<(Value>, Vec>), GraphQLError<'a>> +) -> Result<(Value>, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -259,7 +272,7 @@ pub fn introspect<'a, S, QueryT, MutationT, SubscriptionT>( root_node: &'a RootNode, context: &QueryT::Context, format: IntrospectionFormat, -) -> Result<(Value, Vec>), GraphQLError<'a>> +) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, @@ -278,8 +291,8 @@ where ) } -impl<'a> From>> for GraphQLError<'a> { - fn from(f: Spanning>) -> GraphQLError<'a> { - GraphQLError::ParseError(f) +impl From> for GraphQLError { + fn from(err: Spanning) -> Self { + Self::ParseError(err) } } diff --git a/juniper/src/macros/graphql_vars.rs b/juniper/src/macros/graphql_vars.rs index 56bff3b3..5d0f5562 100644 --- a/juniper/src/macros/graphql_vars.rs +++ b/juniper/src/macros/graphql_vars.rs @@ -217,37 +217,35 @@ mod tests { assert_eq!( graphql_vars! {"key": 123}, - vec![("key".to_owned(), IV::scalar(123))] + vec![("key".into(), IV::scalar(123))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": "val"}, - vec![("key".to_owned(), IV::scalar("val"))] + vec![("key".into(), IV::scalar("val"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": 1.23}, - vec![("key".to_owned(), IV::scalar(1.23))] + vec![("key".into(), IV::scalar(1.23))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": 1 + 2}, - vec![("key".to_owned(), IV::scalar(3))] - .into_iter() - .collect(), + vec![("key".into(), IV::scalar(3))].into_iter().collect(), ); assert_eq!( graphql_vars! {"key": false}, - vec![("key".to_owned(), IV::scalar(false))] + vec![("key".into(), IV::scalar(false))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": (val)}, - vec![("key".to_owned(), IV::scalar(42))] + vec![("key".into(), IV::scalar(42))] .into_iter() .collect::(), ); @@ -257,13 +255,13 @@ mod tests { fn r#enum() { assert_eq!( graphql_vars! {"key": ENUM}, - vec![("key".to_owned(), IV::enum_value("ENUM"))] + vec![("key".into(), IV::enum_value("ENUM"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": lowercase}, - vec![("key".to_owned(), IV::enum_value("lowercase"))] + vec![("key".into(), IV::enum_value("lowercase"))] .into_iter() .collect::(), ); @@ -273,19 +271,19 @@ mod tests { fn variable() { assert_eq!( graphql_vars! {"key": @var}, - vec![("key".to_owned(), IV::variable("var"))] + vec![("key".into(), IV::variable("var"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": @array}, - vec![("key".to_owned(), IV::variable("array"))] + vec![("key".into(), IV::variable("array"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": @object}, - vec![("key".to_owned(), IV::variable("object"))] + vec![("key".into(), IV::variable("object"))] .into_iter() .collect::(), ); @@ -297,68 +295,65 @@ mod tests { assert_eq!( graphql_vars! {"key": []}, - vec![("key".to_owned(), IV::list(vec![]))] + vec![("key".into(), IV::list(vec![]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [null]}, - vec![("key".to_owned(), IV::list(vec![IV::Null]))] + vec![("key".into(), IV::list(vec![IV::Null]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1]}, - vec![("key".to_owned(), IV::list(vec![IV::scalar(1)]))] + vec![("key".into(), IV::list(vec![IV::scalar(1)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1 + 2]}, - vec![("key".to_owned(), IV::list(vec![IV::scalar(3)]))] + vec![("key".into(), IV::list(vec![IV::scalar(3)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [(val)]}, - vec![("key".to_owned(), IV::list(vec![IV::scalar(42)]))] + vec![("key".into(), IV::list(vec![IV::scalar(42)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [ENUM]}, - vec![("key".to_owned(), IV::list(vec![IV::enum_value("ENUM")]))] + vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [lowercase]}, - vec![( - "key".to_owned(), - IV::list(vec![IV::enum_value("lowercase")]) - )] - .into_iter() - .collect::(), + vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))] + .into_iter() + .collect::(), ); assert_eq!( graphql_vars! {"key": [@var]}, - vec![("key".to_owned(), IV::list(vec![IV::variable("var")]))] + vec![("key".into(), IV::list(vec![IV::variable("var")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [@array]}, - vec![("key".to_owned(), IV::list(vec![IV::variable("array")]))] + vec![("key".into(), IV::list(vec![IV::variable("array")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [@object]}, - vec![("key".to_owned(), IV::list(vec![IV::variable("object")]))] + vec![("key".into(), IV::list(vec![IV::variable("object")]))] .into_iter() .collect::(), ); @@ -366,7 +361,7 @@ mod tests { assert_eq!( graphql_vars! {"key": [1, [2], 3]}, vec![( - "key".to_owned(), + "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::scalar(2)]), @@ -379,7 +374,7 @@ mod tests { assert_eq!( graphql_vars! {"key": [1, [2 + 3], 3]}, vec![( - "key".to_owned(), + "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::scalar(5)]), @@ -392,7 +387,7 @@ mod tests { assert_eq!( graphql_vars! {"key": [1, [ENUM], (val)]}, vec![( - "key".to_owned(), + "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::enum_value("ENUM")]), @@ -405,7 +400,7 @@ mod tests { assert_eq!( graphql_vars! {"key": [1 + 2, [(val)], @val]}, vec![( - "key".to_owned(), + "key".into(), IV::list(vec![ IV::scalar(3), IV::list(vec![IV::scalar(42)]), @@ -418,7 +413,7 @@ mod tests { assert_eq!( graphql_vars! {"key": [1, [@val], ENUM]}, vec![( - "key".to_owned(), + "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::variable("val")]), @@ -436,14 +431,14 @@ mod tests { assert_eq!( graphql_vars! {"key": {}}, - vec![("key".to_owned(), IV::object(IndexMap::::new()))] + vec![("key".into(), IV::object(IndexMap::::new()))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": null}}, - vec![("key".to_owned(), IV::object(indexmap! {"key" => IV::Null}))] + vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))] .into_iter() .collect::(), ); @@ -451,7 +446,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": 123}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::scalar(123)}), )] .into_iter() @@ -459,17 +454,14 @@ mod tests { ); assert_eq!( graphql_vars! {"key": {"key": 1 + 2}}, - vec![( - "key".to_owned(), - IV::object(indexmap! {"key" => IV::scalar(3)}), - )] - .into_iter() - .collect::(), + vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)] + .into_iter() + .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": (val)}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::scalar(42)}), )] .into_iter() @@ -479,7 +471,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": []}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::list(vec![])}), )] .into_iter() @@ -488,7 +480,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": [null]}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}), )] .into_iter() @@ -497,7 +489,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": [1]}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}), )] .into_iter() @@ -506,7 +498,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": [1 + 2]}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}), )] .into_iter() @@ -515,7 +507,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": [(val)]}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}), )] .into_iter() @@ -524,7 +516,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": ENUM}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::enum_value("ENUM")}), )] .into_iter() @@ -533,7 +525,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": lowercase}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::enum_value("lowercase")}), )] .into_iter() @@ -542,7 +534,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": @val}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::variable("val")}), )] .into_iter() @@ -551,7 +543,7 @@ mod tests { assert_eq!( graphql_vars! {"key": {"key": @array}}, vec![( - "key".to_owned(), + "key".into(), IV::object(indexmap! {"key" => IV::variable("array")}), )] .into_iter() @@ -576,7 +568,7 @@ mod tests { }, vec![ ( - "inner".to_owned(), + "inner".into(), IV::object(indexmap! { "key1" => IV::scalar(42), "key2" => IV::scalar("val"), @@ -602,7 +594,7 @@ mod tests { ]), }), ), - ("more".to_owned(), IV::variable("var")), + ("more".into(), IV::variable("var")), ] .into_iter() .collect::(), diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index f6565e03..e3e31926 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -41,8 +41,7 @@ where /// [`GraphQLType::name`]: crate::GraphQLType::name pub fn err_unnamed_type(name: &str) -> FieldError { FieldError::from(format!( - "Expected `{}` type to implement `GraphQLType::name`", - name, + "Expected `{name}` type to implement `GraphQLType::name`", )) } diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 155786c8..4af166b7 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -563,6 +563,38 @@ macro_rules! assert_interfaces_impls { }; } +/// Asserts that all [transitive interfaces][0] (the ones implemented by the +/// `$interface`) are also implemented by the `$implementor`. +/// +/// [0]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P +#[macro_export] +macro_rules! assert_transitive_impls { + ($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflect::str_exists_in_arr( + <$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, + <$transitive as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "`: missing `impl = ` for transitive interface `", + <$transitive as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "`." + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + /// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. /// /// This assertion is a combination of [`assert_subtype`] and @@ -863,11 +895,11 @@ macro_rules! const_concat { } const CON: [u8; LEN] = concat([$($s),*]); - // TODO: Use `str::from_utf8()` once it becomes `const`. - // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one - // after another byte-by-byte. - #[allow(unsafe_code)] - unsafe { ::std::str::from_utf8_unchecked(&CON) } + // TODO: Use `.unwrap()` once it becomes `const`. + match ::std::str::from_utf8(&CON) { + ::std::result::Result::Ok(s) => s, + _ => unreachable!(), + } }}; } @@ -985,12 +1017,11 @@ macro_rules! format_type { const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // TODO: Use `str::from_utf8()` once it becomes `const`. - // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one - // after another byte-by-byte. - #[allow(unsafe_code)] - const TYPE_FORMATTED: &str = - unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; + // TODO: Use `.unwrap()` once it becomes `const`. + const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) { + ::std::result::Result::Ok(s) => s, + _ => unreachable!(), + }; TYPE_FORMATTED }}; diff --git a/juniper/src/parser/document.rs b/juniper/src/parser/document.rs index cd891883..cb2b308f 100644 --- a/juniper/src/parser/document.rs +++ b/juniper/src/parser/document.rs @@ -22,7 +22,7 @@ use crate::{ pub fn parse_document_source<'a, 'b, S>( s: &'a str, schema: &'b SchemaType<'b, S>, -) -> UnlocatedParseResult<'a, OwnedDocument<'a, S>> +) -> UnlocatedParseResult> where S: ScalarValue, { @@ -34,7 +34,7 @@ where fn parse_document<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> UnlocatedParseResult<'a, OwnedDocument<'a, S>> +) -> UnlocatedParseResult> where S: ScalarValue, { @@ -52,7 +52,7 @@ where fn parse_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> UnlocatedParseResult<'a, Definition<'a, S>> +) -> UnlocatedParseResult> where S: ScalarValue, { @@ -66,14 +66,14 @@ where Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition( parser, schema, )?)), - _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), + _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_operation_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> ParseResult<'a, Operation<'a, S>> +) -> ParseResult> where S: ScalarValue, { @@ -129,7 +129,7 @@ where fn parse_fragment_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> ParseResult<'a, Fragment<'a, S>> +) -> ParseResult> where S: ScalarValue, { @@ -139,7 +139,7 @@ where let name = match parser.expect_name() { Ok(n) => { if n.item == "on" { - return Err(n.map(|_| ParseError::UnexpectedToken(Token::Name("on")))); + return Err(n.map(|_| ParseError::UnexpectedToken("on".into()))); } else { n } @@ -174,7 +174,7 @@ fn parse_optional_selection_set<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, -) -> OptionParseResult<'a, Vec>> +) -> OptionParseResult>> where S: ScalarValue, { @@ -189,7 +189,7 @@ fn parse_selection_set<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, -) -> ParseResult<'a, Vec>> +) -> ParseResult>> where S: ScalarValue, { @@ -204,7 +204,7 @@ fn parse_selection<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, -) -> UnlocatedParseResult<'a, Selection<'a, S>> +) -> UnlocatedParseResult> where S: ScalarValue, { @@ -218,7 +218,7 @@ fn parse_fragment<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, -) -> UnlocatedParseResult<'a, Selection<'a, S>> +) -> UnlocatedParseResult> where S: ScalarValue, { @@ -292,7 +292,7 @@ where }, ))) } - _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), + _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } @@ -300,7 +300,7 @@ fn parse_field<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, -) -> ParseResult<'a, Field<'a, S>> +) -> ParseResult> where S: ScalarValue, { @@ -351,7 +351,7 @@ fn parse_arguments<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, arguments: Option<&[Argument<'b, S>]>, -) -> OptionParseResult<'a, Arguments<'a, S>> +) -> OptionParseResult> where S: ScalarValue, { @@ -376,7 +376,7 @@ fn parse_argument<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, arguments: Option<&[Argument<'b, S>]>, -) -> ParseResult<'a, (Spanning<&'a str>, Spanning>)> +) -> ParseResult<(Spanning<&'a str>, Spanning>)> where S: ScalarValue, { @@ -395,21 +395,21 @@ where )) } -fn parse_operation_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, OperationType> { +fn parse_operation_type(parser: &mut Parser<'_>) -> ParseResult { match parser.peek().item { Token::Name("query") => Ok(parser.next_token()?.map(|_| OperationType::Query)), Token::Name("mutation") => Ok(parser.next_token()?.map(|_| OperationType::Mutation)), Token::Name("subscription") => { Ok(parser.next_token()?.map(|_| OperationType::Subscription)) } - _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), + _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_variable_definitions<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> OptionParseResult<'a, VariableDefinitions<'a, S>> +) -> OptionParseResult> where S: ScalarValue, { @@ -433,7 +433,7 @@ where fn parse_variable_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> ParseResult<'a, (Spanning<&'a str>, VariableDefinition<'a, S>)> +) -> ParseResult<(Spanning<&'a str>, VariableDefinition<'a, S>)> where S: ScalarValue, { @@ -473,7 +473,7 @@ where fn parse_directives<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> OptionParseResult<'a, Vec>>> +) -> OptionParseResult>>> where S: ScalarValue, { @@ -492,7 +492,7 @@ where fn parse_directive<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, -) -> ParseResult<'a, Directive<'a, S>> +) -> ParseResult> where S: ScalarValue, { @@ -516,7 +516,7 @@ where )) } -pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Type<'a>> { +pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult> { let parsed_type = if let Some(Spanning { start: start_pos, .. }) = parser.skip(&Token::BracketOpen)? @@ -541,10 +541,7 @@ pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Type<'a>> { }) } -fn wrap_non_null<'a>( - parser: &mut Parser<'a>, - inner: Spanning>, -) -> ParseResult<'a, Type<'a>> { +fn wrap_non_null<'a>(parser: &mut Parser<'a>, inner: Spanning>) -> ParseResult> { let Spanning { end: end_pos, .. } = parser.expect(&Token::ExclamationMark)?; let wrapped = match inner.item { diff --git a/juniper/src/parser/lexer.rs b/juniper/src/parser/lexer.rs index c71c7ccf..3eb6a56a 100644 --- a/juniper/src/parser/lexer.rs +++ b/juniper/src/parser/lexer.rs @@ -20,8 +20,8 @@ pub struct Lexer<'a> { /// A single scalar value literal /// /// This is only used for tagging how the lexer has interpreted a value literal -#[derive(Debug, PartialEq, Clone, Copy)] #[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ScalarToken<'a> { String(&'a str), Float(&'a str), @@ -29,8 +29,8 @@ pub enum ScalarToken<'a> { } /// A single token in the input source -#[derive(Debug, PartialEq, Clone, Copy)] #[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Token<'a> { Name(&'a str), Scalar(ScalarToken<'a>), @@ -239,7 +239,7 @@ impl<'a> Lexer<'a> { c if escaped => { return Err(Spanning::zero_width( &old_pos, - LexerError::UnknownEscapeSequence(format!("\\{}", c)), + LexerError::UnknownEscapeSequence(format!("\\{c}")), )); } '\\' => escaped = true, @@ -305,14 +305,14 @@ impl<'a> Lexer<'a> { if len != 4 { return Err(Spanning::zero_width( start_pos, - LexerError::UnknownEscapeSequence("\\u".to_owned() + escape), + LexerError::UnknownEscapeSequence(format!("\\u{escape}")), )); } let code_point = u32::from_str_radix(escape, 16).map_err(|_| { Spanning::zero_width( start_pos, - LexerError::UnknownEscapeSequence("\\u".to_owned() + escape), + LexerError::UnknownEscapeSequence(format!("\\u{escape}")), ) })?; @@ -338,7 +338,7 @@ impl<'a> Lexer<'a> { let mut end_idx = loop { if let Some((idx, ch)) = self.peek_char() { - if ch.is_digit(10) || (ch == '-' && last_idx == start_idx) { + if ch.is_ascii_digit() || (ch == '-' && last_idx == start_idx) { if ch == '0' && last_char == '0' && last_idx == start_idx { return Err(Spanning::zero_width( &self.position, @@ -367,7 +367,7 @@ impl<'a> Lexer<'a> { self.next_char(); end_idx = loop { if let Some((idx, ch)) = self.peek_char() { - if ch.is_digit(10) { + if ch.is_ascii_digit() { self.next_char(); } else if last_idx == start_idx { return Err(Spanning::zero_width( @@ -396,7 +396,9 @@ impl<'a> Lexer<'a> { end_idx = loop { if let Some((idx, ch)) = self.peek_char() { - if ch.is_digit(10) || (last_idx == start_idx && (ch == '-' || ch == '+')) { + if ch.is_ascii_digit() + || (last_idx == start_idx && (ch == '-' || ch == '+')) + { self.next_char(); } else if last_idx == start_idx { // 1e is not a valid floating point number @@ -483,9 +485,9 @@ impl<'a> Iterator for Lexer<'a> { impl<'a> fmt::Display for Token<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Token::Name(name) => write!(f, "{}", name), + Token::Name(name) => write!(f, "{name}"), Token::Scalar(ScalarToken::Int(s)) | Token::Scalar(ScalarToken::Float(s)) => { - write!(f, "{}", s) + write!(f, "{s}") } Token::Scalar(ScalarToken::String(s)) => { write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) @@ -527,15 +529,15 @@ fn is_number_start(c: char) -> bool { impl fmt::Display for LexerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - LexerError::UnknownCharacter(c) => write!(f, "Unknown character \"{}\"", c), + LexerError::UnknownCharacter(c) => write!(f, "Unknown character \"{c}\""), LexerError::UnterminatedString => write!(f, "Unterminated string literal"), LexerError::UnknownCharacterInString(c) => { - write!(f, "Unknown character \"{}\" in string literal", c) + write!(f, "Unknown character \"{c}\" in string literal") } LexerError::UnknownEscapeSequence(ref s) => { - write!(f, "Unknown escape sequence \"{}\" in string", s) + write!(f, "Unknown escape sequence \"{s}\" in string") } - LexerError::UnexpectedCharacter(c) => write!(f, "Unexpected character \"{}\"", c), + LexerError::UnexpectedCharacter(c) => write!(f, "Unexpected character \"{c}\""), LexerError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), LexerError::InvalidNumber => write!(f, "Invalid number literal"), } diff --git a/juniper/src/parser/parser.rs b/juniper/src/parser/parser.rs index 4a066453..b0c27099 100644 --- a/juniper/src/parser/parser.rs +++ b/juniper/src/parser/parser.rs @@ -1,12 +1,16 @@ -use std::{fmt, result::Result}; +use std::{error::Error, fmt, result::Result}; + +use smartstring::alias::String; use crate::parser::{Lexer, LexerError, Spanning, Token}; /// Error while parsing a GraphQL query -#[derive(Debug, PartialEq)] -pub enum ParseError<'a> { +#[derive(Debug, Eq, PartialEq)] +pub enum ParseError { /// An unexpected token occurred in the source - UnexpectedToken(Token<'a>), + // TODO: Previously was `Token<'a>`. + // Revisit on `graphql-parser` integration. + UnexpectedToken(String), /// The input source abruptly ended UnexpectedEndOfFile, @@ -18,14 +22,51 @@ pub enum ParseError<'a> { ExpectedScalarError(&'static str), } -#[doc(hidden)] -pub type ParseResult<'a, T> = Result, Spanning>>; +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedToken(token) => write!(f, "Unexpected \"{token}\""), + Self::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), + Self::LexerError(e) => e.fmt(f), + Self::ExpectedScalarError(e) => e.fmt(f), + } + } +} + +impl Error for ParseError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::LexerError(e) => Some(e), + Self::ExpectedScalarError(_) | Self::UnexpectedToken(_) | Self::UnexpectedEndOfFile => { + None + } + } + } +} + +impl ParseError { + /// Creates a [`ParseError::UnexpectedToken`] out of the provided [`Token`]. + #[must_use] + pub fn unexpected_token(token: Token<'_>) -> Self { + use std::fmt::Write as _; + + let mut s = String::new(); + // PANIC: Unwrapping is OK here, as it may panic only on allocation + // error. + write!(s, "{token}").unwrap(); + + Self::UnexpectedToken(s) + } +} #[doc(hidden)] -pub type UnlocatedParseResult<'a, T> = Result>>; +pub type ParseResult = Result, Spanning>; #[doc(hidden)] -pub type OptionParseResult<'a, T> = Result>, Spanning>>; +pub type UnlocatedParseResult = Result>; + +#[doc(hidden)] +pub type OptionParseResult = Result>, Spanning>; #[doc(hidden)] #[derive(Debug)] @@ -54,7 +95,7 @@ impl<'a> Parser<'a> { } #[doc(hidden)] - pub fn next_token(&mut self) -> ParseResult<'a, Token<'a>> { + pub fn next_token(&mut self) -> ParseResult> { if self.tokens.len() == 1 { Err(Spanning::start_end( &self.peek().start, @@ -67,9 +108,9 @@ impl<'a> Parser<'a> { } #[doc(hidden)] - pub fn expect(&mut self, expected: &Token) -> ParseResult<'a, Token<'a>> { + pub fn expect(&mut self, expected: &Token) -> ParseResult> { if &self.peek().item != expected { - Err(self.next_token()?.map(ParseError::UnexpectedToken)) + Err(self.next_token()?.map(ParseError::unexpected_token)) } else { self.next_token() } @@ -79,7 +120,7 @@ impl<'a> Parser<'a> { pub fn skip( &mut self, expected: &Token, - ) -> Result>>, Spanning>> { + ) -> Result>>, Spanning> { if &self.peek().item == expected { Ok(Some(self.next_token()?)) } else if self.peek().item == Token::EndOfFile { @@ -98,10 +139,10 @@ impl<'a> Parser<'a> { opening: &Token, parser: F, closing: &Token, - ) -> ParseResult<'a, Vec>> + ) -> ParseResult>> where T: fmt::Debug, - F: Fn(&mut Parser<'a>) -> ParseResult<'a, T>, + F: Fn(&mut Parser<'a>) -> ParseResult, { let Spanning { start: start_pos, .. @@ -123,10 +164,10 @@ impl<'a> Parser<'a> { opening: &Token, parser: F, closing: &Token, - ) -> ParseResult<'a, Vec>> + ) -> ParseResult>> where T: fmt::Debug, - F: Fn(&mut Parser<'a>) -> ParseResult<'a, T>, + F: Fn(&mut Parser<'a>) -> ParseResult, { let Spanning { start: start_pos, .. @@ -148,10 +189,10 @@ impl<'a> Parser<'a> { opening: &Token, parser: F, closing: &Token, - ) -> ParseResult<'a, Vec> + ) -> ParseResult> where T: fmt::Debug, - F: Fn(&mut Parser<'a>) -> UnlocatedParseResult<'a, T>, + F: Fn(&mut Parser<'a>) -> UnlocatedParseResult, { let Spanning { start: start_pos, .. @@ -168,7 +209,7 @@ impl<'a> Parser<'a> { } #[doc(hidden)] - pub fn expect_name(&mut self) -> ParseResult<'a, &'a str> { + pub fn expect_name(&mut self) -> ParseResult<&'a str> { match *self.peek() { Spanning { item: Token::Name(_), @@ -188,20 +229,7 @@ impl<'a> Parser<'a> { &self.peek().end, ParseError::UnexpectedEndOfFile, )), - _ => Err(self.next_token()?.map(ParseError::UnexpectedToken)), + _ => Err(self.next_token()?.map(ParseError::unexpected_token)), } } } - -impl<'a> fmt::Display for ParseError<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ParseError::UnexpectedToken(ref token) => write!(f, "Unexpected \"{}\"", token), - ParseError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), - ParseError::LexerError(ref err) => err.fmt(f), - ParseError::ExpectedScalarError(err) => err.fmt(f), - } - } -} - -impl<'a> std::error::Error for ParseError<'a> {} diff --git a/juniper/src/parser/tests/document.rs b/juniper/src/parser/tests/document.rs index bb28074b..65018a3d 100644 --- a/juniper/src/parser/tests/document.rs +++ b/juniper/src/parser/tests/document.rs @@ -16,18 +16,15 @@ where s, &SchemaType::new::(&(), &(), &()), ) - .expect(&format!("Parse error on input {:#?}", s)) + .expect(&format!("Parse error on input {s:#?}")) } -fn parse_document_error<'a, S>(s: &'a str) -> Spanning> -where - S: ScalarValue, -{ +fn parse_document_error(s: &str) -> Spanning { match parse_document_source::( s, &SchemaType::new::(&(), &(), &()), ) { - Ok(doc) => panic!("*No* parse error on input {:#?} =>\n{:#?}", s, doc), + Ok(doc) => panic!("*No* parse error on input {s:#?} =>\n{doc:#?}"), Err(err) => err, } } @@ -136,7 +133,7 @@ fn errors() { Spanning::start_end( &SourcePosition::new(36, 1, 19), &SourcePosition::new(40, 1, 23), - ParseError::UnexpectedToken(Token::Name("Type")) + ParseError::UnexpectedToken("Type".into()) ) ); @@ -145,7 +142,7 @@ fn errors() { Spanning::start_end( &SourcePosition::new(8, 0, 8), &SourcePosition::new(9, 0, 9), - ParseError::UnexpectedToken(Token::CurlyClose) + ParseError::unexpected_token(Token::CurlyClose) ) ); } diff --git a/juniper/src/parser/tests/lexer.rs b/juniper/src/parser/tests/lexer.rs index a08dbfaa..7b295270 100644 --- a/juniper/src/parser/tests/lexer.rs +++ b/juniper/src/parser/tests/lexer.rs @@ -13,8 +13,8 @@ fn tokenize_to_vec<'a>(s: &'a str) -> Vec>> { break; } } - Some(Err(e)) => panic!("Error in input stream: {:#?} for {:#?}", e, s), - None => panic!("EOF before EndOfFile token in {:#?}", s), + Some(Err(e)) => panic!("Error in input stream: {e:#?} for {s:#?}"), + None => panic!("EOF before EndOfFile token in {s:#?}"), } } @@ -37,13 +37,13 @@ fn tokenize_error(s: &str) -> Spanning { match lexer.next() { Some(Ok(t)) => { if t.item == Token::EndOfFile { - panic!("Tokenizer did not return error for {:#?}", s); + panic!("Tokenizer did not return error for {s:#?}"); } } Some(Err(e)) => { return e; } - None => panic!("Tokenizer did not return error for {:#?}", s), + None => panic!("Tokenizer did not return error for {s:#?}"), } } } @@ -196,7 +196,7 @@ fn strings() { Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(34, 0, 34), - Token::Scalar(ScalarToken::String(r#"unicode \u1234\u5678\u90AB\uCDEF"#)) + Token::Scalar(ScalarToken::String(r#"unicode \u1234\u5678\u90AB\uCDEF"#)), ) ); } @@ -207,7 +207,7 @@ fn string_errors() { tokenize_error("\""), Spanning::zero_width( &SourcePosition::new(1, 0, 1), - LexerError::UnterminatedString + LexerError::UnterminatedString, ) ); @@ -215,7 +215,7 @@ fn string_errors() { tokenize_error("\"no end quote"), Spanning::zero_width( &SourcePosition::new(13, 0, 13), - LexerError::UnterminatedString + LexerError::UnterminatedString, ) ); @@ -223,7 +223,7 @@ fn string_errors() { tokenize_error("\"contains unescaped \u{0007} control char\""), Spanning::zero_width( &SourcePosition::new(20, 0, 20), - LexerError::UnknownCharacterInString('\u{0007}') + LexerError::UnknownCharacterInString('\u{0007}'), ) ); @@ -231,7 +231,7 @@ fn string_errors() { tokenize_error("\"null-byte is not \u{0000} end of file\""), Spanning::zero_width( &SourcePosition::new(18, 0, 18), - LexerError::UnknownCharacterInString('\u{0000}') + LexerError::UnknownCharacterInString('\u{0000}'), ) ); @@ -239,7 +239,7 @@ fn string_errors() { tokenize_error("\"multi\nline\""), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnterminatedString + LexerError::UnterminatedString, ) ); @@ -247,7 +247,7 @@ fn string_errors() { tokenize_error("\"multi\rline\""), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnterminatedString + LexerError::UnterminatedString, ) ); @@ -255,7 +255,7 @@ fn string_errors() { tokenize_error(r#""bad \z esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\z".to_owned()) + LexerError::UnknownEscapeSequence("\\z".into()), ) ); @@ -263,7 +263,7 @@ fn string_errors() { tokenize_error(r#""bad \x esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\x".to_owned()) + LexerError::UnknownEscapeSequence("\\x".into()), ) ); @@ -271,7 +271,7 @@ fn string_errors() { tokenize_error(r#""bad \u1 esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\u1".to_owned()) + LexerError::UnknownEscapeSequence("\\u1".into()), ) ); @@ -279,7 +279,7 @@ fn string_errors() { tokenize_error(r#""bad \u0XX1 esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\u0XX1".to_owned()) + LexerError::UnknownEscapeSequence("\\u0XX1".into()), ) ); @@ -287,7 +287,7 @@ fn string_errors() { tokenize_error(r#""bad \uXXXX esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\uXXXX".to_owned()) + LexerError::UnknownEscapeSequence("\\uXXXX".into()), ) ); @@ -295,7 +295,7 @@ fn string_errors() { tokenize_error(r#""bad \uFXXX esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\uFXXX".to_owned()) + LexerError::UnknownEscapeSequence("\\uFXXX".into()), ) ); @@ -303,7 +303,7 @@ fn string_errors() { tokenize_error(r#""bad \uXXXF esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), - LexerError::UnknownEscapeSequence("\\uXXXF".to_owned()) + LexerError::UnknownEscapeSequence("\\uXXXF".into()), ) ); @@ -349,9 +349,7 @@ fn numbers() { Token::Scalar(ScalarToken::Float(actual)) => { assert!( expected == actual, - "[expected] {} != {} [actual]", - expected, - actual + "[expected] {expected} != {actual} [actual]", ); } _ => assert!(false), @@ -662,39 +660,32 @@ fn punctuation_error() { #[test] fn display() { - assert_eq!(format!("{}", Token::Name("identifier")), "identifier"); - - assert_eq!(format!("{}", Token::Scalar(ScalarToken::Int("123"))), "123"); - - assert_eq!( - format!("{}", Token::Scalar(ScalarToken::Float("4.5"))), - "4.5" - ); - - assert_eq!( - format!("{}", Token::Scalar(ScalarToken::String("some string"))), - "\"some string\"" - ); - - assert_eq!( - format!( - "{}", - Token::Scalar(ScalarToken::String("string with \\ escape and \" quote")) + for (input, expected) in [ + (Token::Name("identifier"), "identifier"), + (Token::Scalar(ScalarToken::Int("123")), "123"), + (Token::Scalar(ScalarToken::Float("4.5")), "4.5"), + ( + Token::Scalar(ScalarToken::String("some string")), + "\"some string\"", ), - "\"string with \\\\ escape and \\\" quote\"" - ); - - assert_eq!(format!("{}", Token::ExclamationMark), "!"); - assert_eq!(format!("{}", Token::Dollar), "$"); - assert_eq!(format!("{}", Token::ParenOpen), "("); - assert_eq!(format!("{}", Token::ParenClose), ")"); - assert_eq!(format!("{}", Token::BracketOpen), "["); - assert_eq!(format!("{}", Token::BracketClose), "]"); - assert_eq!(format!("{}", Token::CurlyOpen), "{"); - assert_eq!(format!("{}", Token::CurlyClose), "}"); - assert_eq!(format!("{}", Token::Ellipsis), "..."); - assert_eq!(format!("{}", Token::Colon), ":"); - assert_eq!(format!("{}", Token::Equals), "="); - assert_eq!(format!("{}", Token::At), "@"); - assert_eq!(format!("{}", Token::Pipe), "|"); + ( + Token::Scalar(ScalarToken::String("string with \\ escape and \" quote")), + "\"string with \\\\ escape and \\\" quote\"", + ), + (Token::ExclamationMark, "!"), + (Token::Dollar, "$"), + (Token::ParenOpen, "("), + (Token::ParenClose, ")"), + (Token::BracketOpen, "["), + (Token::BracketClose, "]"), + (Token::CurlyOpen, "{"), + (Token::CurlyClose, "}"), + (Token::Ellipsis, "..."), + (Token::Colon, ":"), + (Token::Equals, "="), + (Token::At, "@"), + (Token::Pipe, "|"), + ] { + assert_eq!(input.to_string(), expected); + } } diff --git a/juniper/src/parser/tests/value.rs b/juniper/src/parser/tests/value.rs index 8dbe78bd..e10a4ab3 100644 --- a/juniper/src/parser/tests/value.rs +++ b/juniper/src/parser/tests/value.rs @@ -65,11 +65,11 @@ where S: ScalarValue, { let mut lexer = Lexer::new(s); - let mut parser = Parser::new(&mut lexer).expect(&format!("Lexer error on input {:#?}", s)); + let mut parser = Parser::new(&mut lexer).expect(&format!("Lexer error on input {s:#?}")); let schema = SchemaType::new::, EmptySubscription<()>>(&(), &(), &()); parse_value_literal(&mut parser, false, &schema, Some(meta)) - .expect(&format!("Parse error on input {:#?}", s)) + .expect(&format!("Parse error on input {s:#?}")) } #[test] diff --git a/juniper/src/parser/utils.rs b/juniper/src/parser/utils.rs index fdad1679..645c3014 100644 --- a/juniper/src/parser/utils.rs +++ b/juniper/src/parser/utils.rs @@ -81,7 +81,7 @@ impl Spanning { } } - /// Modify the contents of the spanned item + /// Modify the contents of the spanned item. pub fn map O>(self, f: F) -> Spanning { Spanning { item: f(self.item), @@ -89,6 +89,13 @@ impl Spanning { end: self.end, } } + + /// Modifies the contents of the spanned item in case `f` returns [`Some`], + /// or returns [`None`] otherwise. + pub fn and_then Option>(self, f: F) -> Option> { + let (start, end) = (self.start, self.end); + f(self.item).map(|item| Spanning { item, start, end }) + } } impl fmt::Display for Spanning { diff --git a/juniper/src/parser/value.rs b/juniper/src/parser/value.rs index 3ff6d5ef..f94f65c2 100644 --- a/juniper/src/parser/value.rs +++ b/juniper/src/parser/value.rs @@ -9,12 +9,12 @@ use crate::{ value::ScalarValue, }; -pub fn parse_value_literal<'a, 'b, S>( - parser: &mut Parser<'a>, +pub fn parse_value_literal<'b, S>( + parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, tpe: Option<&MetaType<'b, S>>, -) -> ParseResult<'a, InputValue> +) -> ParseResult> where S: ScalarValue, { @@ -113,16 +113,16 @@ where }, _, ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), - _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), + _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } -fn parse_list_literal<'a, 'b, S>( - parser: &mut Parser<'a>, +fn parse_list_literal<'b, S>( + parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, tpe: Option<&MetaType<'b, S>>, -) -> ParseResult<'a, InputValue> +) -> ParseResult> where S: ScalarValue, { @@ -135,12 +135,12 @@ where .map(InputValue::parsed_list)) } -fn parse_object_literal<'a, 'b, S>( - parser: &mut Parser<'a>, +fn parse_object_literal<'b, S>( + parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, object_tpe: Option<&InputObjectMeta<'b, S>>, -) -> ParseResult<'a, InputValue> +) -> ParseResult> where S: ScalarValue, { @@ -153,12 +153,12 @@ where .map(|items| InputValue::parsed_object(items.into_iter().map(|s| s.item).collect()))) } -fn parse_object_field<'a, 'b, S>( - parser: &mut Parser<'a>, +fn parse_object_field<'b, S>( + parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, object_meta: Option<&InputObjectMeta<'b, S>>, -) -> ParseResult<'a, (Spanning, Spanning>)> +) -> ParseResult<(Spanning, Spanning>)> where S: ScalarValue, { @@ -179,7 +179,7 @@ where )) } -fn parse_variable_literal<'a, S>(parser: &mut Parser<'a>) -> ParseResult<'a, InputValue> +fn parse_variable_literal(parser: &mut Parser<'_>) -> ParseResult> where S: ScalarValue, { @@ -199,12 +199,12 @@ where )) } -fn parse_scalar_literal_by_infered_type<'a, 'b, S>( - token: ScalarToken<'a>, +fn parse_scalar_literal_by_infered_type<'b, S>( + token: ScalarToken<'_>, start: &SourcePosition, end: &SourcePosition, schema: &'b SchemaType<'b, S>, -) -> ParseResult<'a, InputValue> +) -> ParseResult> where S: ScalarValue, { diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 2ba1faa4..04f5eda9 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -16,7 +16,7 @@ use crate::{ }; /// Whether an item is deprecated, with context. -#[derive(Debug, PartialEq, Hash, Clone)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum DeprecationStatus { /// The field/variant is not deprecated. Current, @@ -58,7 +58,7 @@ pub struct ScalarMeta<'a, S> { pub type InputValueParseFn = for<'b> fn(&'b InputValue) -> Result<(), FieldError>; /// Shortcut for a [`ScalarToken`] parsing function. -pub type ScalarTokenParseFn = for<'b> fn(ScalarToken<'b>) -> Result>; +pub type ScalarTokenParseFn = for<'b> fn(ScalarToken<'b>) -> Result; /// List type metadata #[derive(Debug)] @@ -110,6 +110,8 @@ pub struct InterfaceMeta<'a, S> { pub description: Option, #[doc(hidden)] pub fields: Vec>, + #[doc(hidden)] + pub interface_names: Vec, } /// Union type metadata @@ -403,7 +405,7 @@ impl<'a, S> MetaType<'a, S> { // "used exclusively by GraphQL’s introspection system" { name.starts_with("__") || - // + // https://spec.graphql.org/October2021#sec-Scalars name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" || // Our custom empty markers name == "_EmptyMutation" || name == "_EmptySubscription" @@ -453,7 +455,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -523,7 +525,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -534,7 +536,7 @@ impl<'a, S> ObjectMeta<'a, S> { pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { self.interface_names = interfaces .iter() - .map(|t| t.innermost_name().to_owned()) + .map(|t| t.innermost_name().into()) .collect(); self } @@ -566,7 +568,7 @@ impl<'a, S> EnumMeta<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -587,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> { name, description: None, fields: fields.to_vec(), + interface_names: Vec::new(), } } @@ -595,7 +598,19 @@ impl<'a, S> InterfaceMeta<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); + self + } + + /// Sets the `interfaces` this [`InterfaceMeta`] interface implements. + /// + /// Overwrites any previously set list of interfaces. + #[must_use] + pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { + self.interface_names = interfaces + .iter() + .map(|t| t.innermost_name().into()) + .collect(); self } @@ -612,10 +627,7 @@ impl<'a> UnionMeta<'a> { Self { name, description: None, - of_type_names: of_types - .iter() - .map(|t| t.innermost_name().to_owned()) - .collect(), + of_type_names: of_types.iter().map(|t| t.innermost_name().into()).collect(), } } @@ -624,7 +636,7 @@ impl<'a> UnionMeta<'a> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -656,7 +668,7 @@ impl<'a, S> InputObjectMeta<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -672,7 +684,7 @@ impl<'a, S> Field<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -697,7 +709,7 @@ impl<'a, S> Field<'a, S> { /// Overwrites any previously set deprecation reason. #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { - self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); + self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into)); self } } @@ -706,7 +718,7 @@ impl<'a, S> Argument<'a, S> { /// Builds a new [`Argument`] of the given [`Type`] with the given `name`. pub fn new(name: &str, arg_type: Type<'a>) -> Self { Self { - name: name.to_owned(), + name: name.into(), description: None, arg_type, default_value: None, @@ -718,7 +730,7 @@ impl<'a, S> Argument<'a, S> { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -736,7 +748,7 @@ impl EnumValue { /// Constructs a new [`EnumValue`] with the provided `name`. pub fn new(name: &str) -> Self { Self { - name: name.to_owned(), + name: name.into(), description: None, deprecation_status: DeprecationStatus::Current, } @@ -747,7 +759,7 @@ impl EnumValue { /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } @@ -756,7 +768,7 @@ impl EnumValue { /// Overwrites any previously set deprecation reason. #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { - self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); + self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into)); self } } diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 5544fbbd..e7cba411 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -167,8 +167,7 @@ where /// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language) /// format. pub fn as_schema_language(&self) -> String { - let doc = self.as_parser_document(); - format!("{}", doc) + self.as_parser_document().to_string() } #[cfg(feature = "graphql-parser")] @@ -210,17 +209,14 @@ impl<'a, S> SchemaType<'a, S> { registry.get_type::>(&()); - directives.insert("skip".to_owned(), DirectiveType::new_skip(&mut registry)); + directives.insert("skip".into(), DirectiveType::new_skip(&mut registry)); + directives.insert("include".into(), DirectiveType::new_include(&mut registry)); directives.insert( - "include".to_owned(), - DirectiveType::new_include(&mut registry), - ); - directives.insert( - "deprecated".to_owned(), + "deprecated".into(), DirectiveType::new_deprecated(&mut registry), ); directives.insert( - "specifiedBy".to_owned(), + "specifiedBy".into(), DirectiveType::new_specified_by(&mut registry), ); @@ -243,7 +239,7 @@ impl<'a, S> SchemaType<'a, S> { for meta_type in registry.types.values() { if let MetaType::Placeholder(PlaceholderMeta { ref of_type }) = *meta_type { - panic!("Type {:?} is still a placeholder type", of_type); + panic!("Type {of_type:?} is still a placeholder type"); } } SchemaType { @@ -508,9 +504,9 @@ where locations: &[DirectiveLocation], arguments: &[Argument<'a, S>], is_repeatable: bool, - ) -> DirectiveType<'a, S> { - DirectiveType { - name: name.to_owned(), + ) -> Self { + Self { + name: name.into(), description: None, locations: locations.to_vec(), arguments: arguments.to_vec(), @@ -578,7 +574,7 @@ where } pub fn description(mut self, description: &str) -> DirectiveType<'a, S> { - self.description = Some(description.to_owned()); + self.description = Some(description.into()); self } } @@ -605,8 +601,8 @@ impl<'a, S> fmt::Display for TypeType<'a, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Concrete(t) => f.write_str(t.name().unwrap()), - Self::List(i, _) => write!(f, "[{}]", i), - Self::NonNull(i) => write!(f, "{}!", i), + Self::List(i, _) => write!(f, "[{i}]"), + Self::NonNull(i) => write!(f, "{i}!"), } } } @@ -644,10 +640,7 @@ mod test { "#, ) .unwrap(); - assert_eq!( - format!("{}", ast), - format!("{}", schema.as_parser_document()), - ); + assert_eq!(ast.to_string(), schema.as_parser_document().to_string()); } } @@ -691,10 +684,10 @@ mod test { } /// This is whatever's description. fn whatever() -> String { - "foo".to_string() + "foo".into() } fn arr(stuff: Vec) -> Option<&'static str> { - (!stuff.is_empty()).then(|| "stuff") + (!stuff.is_empty()).then_some("stuff") } fn fruit() -> Fruit { Fruit::Apple @@ -754,7 +747,7 @@ mod test { "#, ) .unwrap(); - assert_eq!(format!("{}", ast), schema.as_schema_language()); + assert_eq!(ast.to_string(), schema.as_schema_language()); } } } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 30906478..61822746 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -211,13 +211,19 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option>> { + fn fields( + &self, + #[graphql(default = false)] include_deprecated: Option, + ) -> Option>> { match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( fields .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .filter(|f| { + include_deprecated.unwrap_or_default() + || !f.deprecation_status.is_deprecated() + }) .filter(|f| !f.name.starts_with("__")) .collect(), ), @@ -228,7 +234,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn of_type(&self) -> Option<&TypeType> { match self { TypeType::Concrete(_) => None, - TypeType::List(l, _) | TypeType::NonNull(l) => Some(&*l), + TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l), } } @@ -244,10 +250,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { match self { - TypeType::Concrete(&MetaType::Object(ObjectMeta { - ref interface_names, - .. - })) => Some( + TypeType::Concrete( + &MetaType::Object(ObjectMeta { + ref interface_names, + .. + }) + | &MetaType::Interface(InterfaceMeta { + ref interface_names, + .. + }), + ) => Some( interface_names .iter() .filter_map(|n| context.type_by_name(n)) @@ -276,16 +288,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { .iter() .filter_map(|&ct| { if let MetaType::Object(ObjectMeta { - ref name, - ref interface_names, + name, + interface_names, .. - }) = *ct + }) = ct { - if interface_names.contains(&iface_name.to_string()) { - context.type_by_name(name) - } else { - None - } + interface_names + .iter() + .any(|name| name == iface_name) + .then(|| context.type_by_name(name)) + .flatten() } else { None } @@ -296,12 +308,18 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option> { + fn enum_values( + &self, + #[graphql(default = false)] include_deprecated: Option, + ) -> Option> { match self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values .iter() - .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .filter(|f| { + include_deprecated.unwrap_or_default() + || !f.deprecation_status.is_deprecated() + }) .collect(), ), _ => None, diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index dc0ab05d..6051965b 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -190,8 +190,11 @@ impl GraphQLParserTranslator { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), - // TODO: Support this with GraphQL October 2021 Edition. - implements_interfaces: vec![], + implements_interfaces: x + .interface_names + .iter() + .map(|s| From::from(s.as_str())) + .collect(), directives: vec![], fields: x .fields @@ -282,22 +285,18 @@ where DeprecationStatus::Current => None, DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { position: Pos::default(), - name: From::from("deprecated"), - arguments: if let Some(reason) = reason { - vec![( - From::from("reason"), - ExternalValue::String(reason.to_string()), - )] - } else { - vec![] - }, + name: "deprecated".into(), + arguments: reason + .as_ref() + .map(|rsn| vec![(From::from("reason"), ExternalValue::String(rsn.into()))]) + .unwrap_or_default(), }), } } -// Right now the only directive supported is `@deprecated`. `@skip` and `@include` -// are dealt with elsewhere. -// +// Right now the only directive supported is `@deprecated`. +// `@skip` and `@include` are dealt with elsewhere. +// https://spec.graphql.org/October2021#sec-Type-System.Directives.Built-in-Directives fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec> where T: Text<'a>, diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index d9c795de..d89af6bc 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -93,12 +93,12 @@ impl Human { home_planet: Option<&str>, ) -> Self { Self { - id: id.to_owned(), - name: name.to_owned(), - friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), + id: id.into(), + name: name.into(), + friend_ids: friend_ids.iter().copied().map(Into::into).collect(), appears_in: appears_in.to_vec(), - secret_backstory: secret_backstory.map(ToOwned::to_owned), - home_planet: home_planet.map(|p| p.to_owned()), + secret_backstory: secret_backstory.map(Into::into), + home_planet: home_planet.map(Into::into), } } } @@ -153,12 +153,12 @@ impl Droid { primary_function: Option<&str>, ) -> Self { Self { - id: id.to_owned(), - name: name.to_owned(), - friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), + id: id.into(), + name: name.into(), + friend_ids: friend_ids.iter().copied().map(Into::into).collect(), appears_in: appears_in.to_vec(), - secret_backstory: secret_backstory.map(ToOwned::to_owned), - primary_function: primary_function.map(ToOwned::to_owned), + secret_backstory: secret_backstory.map(Into::into), + primary_function: primary_function.map(Into::into), } } } @@ -192,7 +192,7 @@ impl Droid { } } -#[derive(Default, Clone)] +#[derive(Clone, Default)] pub struct Database { humans: HashMap, droids: HashMap, @@ -206,7 +206,7 @@ impl Database { let mut droids = HashMap::new(); humans.insert( - "1000".to_owned(), + "1000".into(), Human::new( "1000", "Luke Skywalker", @@ -218,7 +218,7 @@ impl Database { ); humans.insert( - "1001".to_owned(), + "1001".into(), Human::new( "1001", "Darth Vader", @@ -230,7 +230,7 @@ impl Database { ); humans.insert( - "1002".to_owned(), + "1002".into(), Human::new( "1002", "Han Solo", @@ -242,7 +242,7 @@ impl Database { ); humans.insert( - "1003".to_owned(), + "1003".into(), Human::new( "1003", "Leia Organa", @@ -254,7 +254,7 @@ impl Database { ); humans.insert( - "1004".to_owned(), + "1004".into(), Human::new( "1004", "Wilhuff Tarkin", @@ -266,7 +266,7 @@ impl Database { ); droids.insert( - "2000".to_owned(), + "2000".into(), Droid::new( "2000", "C-3PO", @@ -278,7 +278,7 @@ impl Database { ); droids.insert( - "2001".to_owned(), + "2001".into(), Droid::new( "2001", "R2-D2", diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 1e1b03e0..d2e4643e 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -1173,7 +1173,7 @@ pub(crate) fn schema_introspection_result() -> Value { } ], "inputFields": null, - "interfaces": null, + "interfaces": [], "enumValues": null, "possibleTypes": [ { @@ -2500,7 +2500,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { } ], "inputFields": null, - "interfaces": null, + "interfaces": [], "enumValues": null, "possibleTypes": [ { diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs index 6ba01a76..8c73486b 100644 --- a/juniper/src/tests/subscriptions.rs +++ b/juniper/src/tests/subscriptions.rs @@ -1,4 +1,4 @@ -use std::{iter, iter::FromIterator as _, pin::Pin}; +use std::{iter, pin::Pin}; use futures::{stream, StreamExt as _}; @@ -48,9 +48,9 @@ impl MySubscription { async fn async_human() -> HumanStream { Box::pin(stream::once(async { Human { - id: "stream id".to_string(), - name: "stream name".to_string(), - home_planet: "stream home planet".to_string(), + id: "stream id".into(), + name: "stream name".into(), + home_planet: "stream home planet".into(), } })) } @@ -78,7 +78,7 @@ impl MySubscription { Human { id, name, - home_planet: "default home planet".to_string(), + home_planet: "default home planet".into(), } })) } @@ -154,10 +154,10 @@ fn returns_requested_object() { id name } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter( @@ -182,10 +182,9 @@ fn returns_error() { id name } - }"# - .to_string(); + }"#; - let response = create_and_execute(query); + let response = create_and_execute(query.into()); assert!(response.is_err()); @@ -206,10 +205,10 @@ fn can_access_context() { humanWithContext { id } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( @@ -234,10 +233,10 @@ fn resolves_typed_inline_fragments() { id } } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( @@ -262,10 +261,10 @@ fn resolves_nontyped_inline_fragments() { id } } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( @@ -289,10 +288,10 @@ fn can_access_arguments() { id name } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( @@ -317,10 +316,10 @@ fn type_alias() { id name } - }"# - .to_string(); + }"#; - let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); + let (names, collected_values) = + create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( diff --git a/juniper/src/tests/type_info_tests.rs b/juniper/src/tests/type_info_tests.rs index 61e7662b..6b43a7ef 100644 --- a/juniper/src/tests/type_info_tests.rs +++ b/juniper/src/tests/type_info_tests.rs @@ -75,15 +75,15 @@ fn test_node() { baz }"#; let node_info = NodeTypeInfo { - name: "MyNode".to_string(), - attribute_names: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], + name: "MyNode".into(), + attribute_names: vec!["foo".into(), "bar".into(), "baz".into()], }; let mut node = Node { attributes: IndexMap::new(), }; - node.attributes.insert("foo".to_string(), "1".to_string()); - node.attributes.insert("bar".to_string(), "2".to_string()); - node.attributes.insert("baz".to_string(), "3".to_string()); + node.attributes.insert("foo".into(), "1".into()); + node.attributes.insert("bar".into(), "2".into()); + node.attributes.insert("baz".into(), "3".into()); let schema: RootNode<_, _, _> = RootNode::new_with_info( node, EmptyMutation::new(), diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index e3273275..827a1d50 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -1,3 +1,5 @@ +use std::future; + use crate::{ ast::Selection, executor::{ExecutionResult, Executor}, @@ -30,7 +32,7 @@ where /// /// The default implementation panics. /// - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_field_async<'a>( &'a self, _info: &'a Self::TypeInfo, @@ -54,9 +56,9 @@ where /// /// The default implementation panics. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Unions - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Unions + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_into_type_async<'a>( &'a self, info: &'a Self::TypeInfo, @@ -91,8 +93,8 @@ where /// /// The default implementation panics, if `selection_set` is [`None`]. /// - /// [0]: https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [0]: https://spec.graphql.org/October2021#sec-Handling-Field-Errors + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, @@ -226,7 +228,7 @@ where panic!( "Field {} not found on type {:?}", f.name.item, - meta_type.name() + meta_type.name(), ) }); @@ -242,7 +244,9 @@ where f.arguments.as_ref().map(|m| { m.item .iter() - .map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) + .filter_map(|&(ref k, ref v)| { + v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) + }) .collect() }), &meta_field.arguments, @@ -252,7 +256,7 @@ where let is_non_null = meta_field.field_type.is_non_null(); let response_name = response_name.to_string(); - async_values.push(AsyncValueFuture::Field(async move { + async_values.push_back(AsyncValueFuture::Field(async move { // TODO: implement custom future type instead of // two-level boxing. let res = instance @@ -315,12 +319,12 @@ where if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { - async_values.push(AsyncValueFuture::FragmentSpread(async move { - AsyncValue::Field(AsyncField { + async_values.push_back(AsyncValueFuture::FragmentSpread( + future::ready(AsyncValue::Field(AsyncField { name: k, value: Some(v), - }) - })); + })), + )); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, *start_pos); @@ -360,19 +364,19 @@ where if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { - async_values.push(AsyncValueFuture::InlineFragment1(async move { - AsyncValue::Field(AsyncField { + async_values.push_back(AsyncValueFuture::InlineFragment1( + future::ready(AsyncValue::Field(AsyncField { name: k, value: Some(v), - }) - })); + })), + )); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, *start_pos); } } } else { - async_values.push(AsyncValueFuture::InlineFragment2(async move { + async_values.push_back(AsyncValueFuture::InlineFragment2(async move { let value = resolve_selection_set_into_async( instance, info, diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index d5c9e394..9d54fee0 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -49,7 +49,6 @@ pub enum TypeKind { /// ## Input objects /// /// Represents complex values provided in queries _into_ the system. - #[graphql(name = "INPUT_OBJECT")] InputObject, /// ## List types @@ -63,7 +62,6 @@ pub enum TypeKind { /// /// In GraphQL, nullable types are the default. By putting a `!` after a\ /// type, it becomes non-nullable. - #[graphql(name = "NON_NULL")] NonNull, } @@ -89,7 +87,7 @@ impl<'a, S> Arguments<'a, S> { if let (Some(args), Some(meta_args)) = (&mut args, meta_args) { for arg in meta_args { let arg_name = arg.name.as_str(); - if args.get(arg_name).map_or(true, InputValue::is_null) { + if args.get(arg_name).is_none() { if let Some(val) = arg.default_value.as_ref() { args.insert(arg_name, val.clone()); } @@ -150,14 +148,14 @@ impl<'a, S> Arguments<'a, S> { /// This trait is intended to be used in a conjunction with a [`GraphQLType`] trait. See the example /// in the documentation of a [`GraphQLType`] trait. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Unions -/// [3]: https://spec.graphql.org/June2018/#sec-Objects -/// [4]: https://spec.graphql.org/June2018/#sec-Scalars -/// [5]: https://spec.graphql.org/June2018/#sec-Enums -/// [6]: https://spec.graphql.org/June2018/#sec-Type-System.List -/// [7]: https://spec.graphql.org/June2018/#sec-Type-System.Non-Null -/// [8]: https://spec.graphql.org/June2018/#sec-Input-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces +/// [2]: https://spec.graphql.org/October2021#sec-Unions +/// [3]: https://spec.graphql.org/October2021#sec-Objects +/// [4]: https://spec.graphql.org/October2021#sec-Scalars +/// [5]: https://spec.graphql.org/October2021#sec-Enums +/// [6]: https://spec.graphql.org/October2021#sec-List +/// [7]: https://spec.graphql.org/October2021#sec-Non-Null +/// [8]: https://spec.graphql.org/October2021#sec-Input-Objects /// [11]: https://doc.rust-lang.org/reference/items/traits.html#object-safety /// [12]: https://doc.rust-lang.org/reference/types/trait-object.html pub trait GraphQLValue @@ -196,7 +194,7 @@ where /// /// The default implementation panics. /// - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_field( &self, _info: &Self::TypeInfo, @@ -217,9 +215,9 @@ where /// /// The default implementation panics. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Unions - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Unions + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_into_type( &self, info: &Self::TypeInfo, @@ -243,9 +241,9 @@ where /// /// The default implementation panics. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Unions - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Unions + /// [3]: https://spec.graphql.org/October2021#sec-Objects #[allow(unused_variables)] fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { panic!( @@ -271,8 +269,8 @@ where /// /// The default implementation panics, if `selection_set` is [`None`]. /// - /// [0]: https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability - /// [3]: https://spec.graphql.org/June2018/#sec-Objects + /// [0]: https://spec.graphql.org/October2021#sec-Errors-and-Non-Nullability + /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve( &self, info: &Self::TypeInfo, @@ -379,13 +377,13 @@ where /// // schema in `meta()` above, or a validation failed because of a this library bug. /// // /// // In either of those two cases, the only reasonable way out is to panic the thread. -/// _ => panic!("Field {} not found on type User", field_name), +/// _ => panic!("Field {field_name} not found on type User"), /// } /// } /// } /// ``` /// -/// [3]: https://spec.graphql.org/June2018/#sec-Objects +/// [3]: https://spec.graphql.org/October2021#sec-Objects pub trait GraphQLType: GraphQLValue where S: ScalarValue, @@ -454,7 +452,7 @@ where panic!( "Field {} not found on type {:?}", f.name.item, - meta_type.name() + meta_type.name(), ) }); @@ -474,8 +472,8 @@ where f.arguments.as_ref().map(|m| { m.item .iter() - .map(|&(ref k, ref v)| { - (k.item, v.item.clone().into_const(exec_vars)) + .filter_map(|&(ref k, ref v)| { + v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) }) .collect() }), @@ -608,7 +606,7 @@ where .arguments .iter() .flat_map(|m| m.item.get("if")) - .flat_map(|v| v.item.clone().into_const(vars).convert()) + .filter_map(|v| v.item.clone().into_const(vars)?.convert().ok()) .next() .unwrap(); diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index e8b6d6e0..2c400088 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -580,12 +580,11 @@ where fn into_field_error(self) -> FieldError { const ERROR_PREFIX: &str = "Failed to convert into exact-size array"; match self { - Self::Null => format!("{}: Value cannot be `null`", ERROR_PREFIX).into(), - Self::WrongCount { actual, expected } => format!( - "{}: wrong elements count: {} instead of {}", - ERROR_PREFIX, actual, expected - ) - .into(), + Self::Null => format!("{ERROR_PREFIX}: Value cannot be `null`").into(), + Self::WrongCount { actual, expected } => { + format!("{ERROR_PREFIX}: wrong elements count: {actual} instead of {expected}",) + .into() + } Self::Item(s) => s.into_field_error(), } } diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 15dc1810..dc966147 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -17,23 +17,23 @@ use crate::{GraphQLType, ScalarValue}; /// [GraphQL objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5] /// and [unions][6]) are not allowed. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Objects -/// [2]: https://spec.graphql.org/June2018/#sec-Scalars -/// [3]: https://spec.graphql.org/June2018/#sec-Enums -/// [4]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects -/// [6]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Enums +/// [4]: https://spec.graphql.org/October2021#sec-Interfaces +/// [5]: https://spec.graphql.org/October2021#sec-Input-Objects +/// [6]: https://spec.graphql.org/October2021#sec-Unions pub trait GraphQLObject: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// - /// [1]: https://spec.graphql.org/June2018/ + /// [1]: https://spec.graphql.org/October2021 fn mark() {} } -impl<'a, S, T> GraphQLObject for &T +impl GraphQLObject for &T where T: GraphQLObject + ?Sized, S: ScalarValue, @@ -74,23 +74,23 @@ where /// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] /// and [unions][6]) are not allowed. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Scalars -/// [3]: https://spec.graphql.org/June2018/#sec-Enums -/// [4]: https://spec.graphql.org/June2018/#sec-Objects -/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects -/// [6]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Enums +/// [4]: https://spec.graphql.org/October2021#sec-Objects +/// [5]: https://spec.graphql.org/October2021#sec-Input-Objects +/// [6]: https://spec.graphql.org/October2021#sec-Unions pub trait GraphQLInterface: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// - /// [1]: https://spec.graphql.org/June2018/ + /// [1]: https://spec.graphql.org/October2021 fn mark() {} } -impl<'a, S, T> GraphQLInterface for &T +impl GraphQLInterface for &T where T: GraphQLInterface + ?Sized, S: ScalarValue, @@ -131,23 +131,23 @@ where /// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and /// [interfaces][6]) are not allowed. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions -/// [2]: https://spec.graphql.org/June2018/#sec-Scalars -/// [3]: https://spec.graphql.org/June2018/#sec-Enums -/// [4]: https://spec.graphql.org/June2018/#sec-Objects -/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects -/// [6]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Unions +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Enums +/// [4]: https://spec.graphql.org/October2021#sec-Objects +/// [5]: https://spec.graphql.org/October2021#sec-Input-Objects +/// [6]: https://spec.graphql.org/October2021#sec-Interfaces pub trait GraphQLUnion: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// - /// [1]: https://spec.graphql.org/June2018/ + /// [1]: https://spec.graphql.org/October2021 fn mark() {} } -impl<'a, S, T> GraphQLUnion for &T +impl GraphQLUnion for &T where T: GraphQLUnion + ?Sized, S: ScalarValue, @@ -194,7 +194,7 @@ pub trait IsOutputType: GraphQLType { fn mark() {} } -impl<'a, S, T> IsOutputType for &T +impl IsOutputType for &T where T: IsOutputType + ?Sized, S: ScalarValue, @@ -282,7 +282,7 @@ where } } -impl<'a, S> IsOutputType for str where S: ScalarValue {} +impl IsOutputType for str where S: ScalarValue {} /// Marker trait for types which can be used as input types. /// @@ -298,7 +298,7 @@ pub trait IsInputType: GraphQLType { fn mark() {} } -impl<'a, S, T> IsInputType for &T +impl IsInputType for &T where T: IsInputType + ?Sized, S: ScalarValue, @@ -375,4 +375,4 @@ where } } -impl<'a, S> IsInputType for str where S: ScalarValue {} +impl IsInputType for str where S: ScalarValue {} diff --git a/juniper/src/types/name.rs b/juniper/src/types/name.rs index eae555d9..fbf5304d 100644 --- a/juniper/src/types/name.rs +++ b/juniper/src/types/name.rs @@ -50,11 +50,10 @@ impl FromStr for Name { type Err = NameParseError; fn from_str(s: &str) -> Result { if Name::is_valid(s) { - Ok(Name(s.to_string())) + Ok(Name(s.into())) } else { Err(NameParseError(format!( - "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", - s + "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{s}\" does not", ))) } } diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 7e8d01cc..20f16708 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -32,12 +32,16 @@ use crate::{ pub enum Nullable { /// No value ImplicitNull, + /// No value, explicitly specified to be null ExplicitNull, + /// Some value `T` Some(T), } +// Implemented manually to omit redundant `T: Default` trait bound, imposed by +// `#[derive(Default)]`. impl Default for Nullable { fn default() -> Self { Self::ImplicitNull diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 0726ef52..17e70679 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -265,7 +265,7 @@ where } } -impl<'e, S, T> GraphQLValueAsync for Arc +impl GraphQLValueAsync for Arc where T: GraphQLValueAsync + Send + ?Sized, T::TypeInfo: Sync, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 66e26e74..54efa392 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -37,7 +37,7 @@ impl ID { .map(str::to_owned) .or_else(|| v.as_int_value().as_ref().map(ToString::to_string)) .map(Self) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } } @@ -81,10 +81,10 @@ mod impl_string_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); @@ -120,7 +120,7 @@ mod impl_string_scalar { } Some(s) => { return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence( - format!("\\{}", s), + format!("\\{s}"), ))) } None => return Err(ParseError::LexerError(LexerError::UnterminatedString)), @@ -132,12 +132,12 @@ mod impl_string_scalar { } Ok(ret.into()) } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) + Err(ParseError::unexpected_token(Token::Scalar(value))) } } } -fn parse_unicode_codepoint<'a, I>(char_iter: &mut I) -> Result> +fn parse_unicode_codepoint(char_iter: &mut I) -> Result where I: Iterator, { @@ -149,19 +149,16 @@ where .and_then(|c1| { char_iter .next() - .map(|c2| format!("{}{}", c1, c2)) + .map(|c2| format!("{c1}{c2}")) .ok_or_else(|| { - ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{}", c1))) + ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{c1}"))) }) }) .and_then(|mut s| { char_iter .next() .ok_or_else(|| { - ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( - "\\u{}", - s.clone() - ))) + ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}"))) }) .map(|c2| { s.push(c2); @@ -172,10 +169,7 @@ where char_iter .next() .ok_or_else(|| { - ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( - "\\u{}", - s.clone() - ))) + ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}"))) }) .map(|c2| { s.push(c2); @@ -184,14 +178,12 @@ where })?; let code_point = u32::from_str_radix(&escaped_code_point, 16).map_err(|_| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( - "\\u{}", - escaped_code_point + "\\u{escaped_code_point}", ))) })?; char::from_u32(code_point).ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( - "\\u{}", - escaped_code_point + "\\u{escaped_code_point}", ))) }) } @@ -282,12 +274,12 @@ mod impl_boolean_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_bool) - .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) + .ok_or_else(|| format!("Expected `Boolean`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { // `Boolean`s are parsed separately, they shouldn't reach this code path. - Err(ParseError::UnexpectedToken(Token::Scalar(value))) + Err(ParseError::unexpected_token(Token::Scalar(value))) } } @@ -303,16 +295,16 @@ mod impl_int_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `Int`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::Int(v) = value { v.parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: i32| s.into()) } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) + Err(ParseError::unexpected_token(Token::Scalar(value))) } } } @@ -329,20 +321,20 @@ mod impl_float_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() - .ok_or_else(|| format!("Expected `Float`, found: {}", v)) + .ok_or_else(|| format!("Expected `Float`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { match value { ScalarToken::Int(v) => v .parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: i32| f64::from(s).into()), ScalarToken::Float(v) => v .parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: f64| s.into()), - ScalarToken::String(_) => Err(ParseError::UnexpectedToken(Token::Scalar(value))), + ScalarToken::String(_) => Err(ParseError::unexpected_token(Token::Scalar(value))), } } } @@ -401,8 +393,9 @@ where { } +// Implemented manually to omit redundant `T: Default` trait bound, imposed by +// `#[derive(Default)]`. impl Default for EmptyMutation { - #[inline] fn default() -> Self { Self::new() } @@ -461,8 +454,9 @@ where { } +// Implemented manually to omit redundant `T: Default` trait bound, imposed by +// `#[derive(Default)]`. impl Default for EmptySubscription { - #[inline] fn default() -> Self { Self::new() } @@ -499,8 +493,8 @@ mod tests { #[test] fn test_id_display() { - let id = ID(String::from("foo")); - assert_eq!(format!("{}", id), "foo"); + let id = ID("foo".into()); + assert_eq!(id.to_string(), "foo"); } #[test] @@ -508,7 +502,7 @@ mod tests { fn parse_string(s: &str, expected: &str) { let s = >::from_str(ScalarToken::String(s)); - assert!(s.is_ok(), "A parsing error occurred: {:?}", s); + assert!(s.is_ok(), "A parsing error occurred: {s:?}"); let s: Option = s.unwrap().into(); assert!(s.is_some(), "No string returned"); assert_eq!(s.unwrap(), expected); @@ -527,7 +521,7 @@ mod tests { #[test] fn parse_f64_from_int() { - for (v, expected) in &[ + for (v, expected) in [ ("0", 0), ("128", 128), ("1601942400", 1601942400), @@ -538,14 +532,14 @@ mod tests { assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); let n: Option = n.unwrap().into(); - assert!(n.is_some(), "No f64 returned"); - assert_eq!(n.unwrap(), f64::from(*expected)); + assert!(n.is_some(), "No `f64` returned"); + assert_eq!(n.unwrap(), f64::from(expected)); } } #[test] fn parse_f64_from_float() { - for (v, expected) in &[ + for (v, expected) in [ ("0.", 0.), ("1.2", 1.2), ("1601942400.", 1601942400.), @@ -556,8 +550,8 @@ mod tests { assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); let n: Option = n.unwrap().into(); - assert!(n.is_some(), "No f64 returned"); - assert_eq!(n.unwrap(), *expected); + assert!(n.is_some(), "No `f64` returned"); + assert_eq!(n.unwrap(), expected); } } diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index e99a5aaf..e1f42fdf 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -94,8 +94,8 @@ pub trait SubscriptionConnection: futures::Stream> /// /// See trait methods for more detailed explanation on how this trait works. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Subscription -/// [2]: https://spec.graphql.org/June2018/#sec-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Subscription +/// [2]: https://spec.graphql.org/October2021#sec-Objects pub trait GraphQLSubscriptionValue: GraphQLValue + Sync where Self::TypeInfo: Sync, @@ -204,7 +204,7 @@ crate::sa::assert_obj_safe!(GraphQLSubscriptionValue: GraphQLSubscriptionValue + GraphQLType where @@ -316,7 +316,9 @@ where f.arguments.as_ref().map(|m| { m.item .iter() - .map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) + .filter_map(|&(ref k, ref v)| { + v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) + }) .collect() }), &meta_field.arguments, diff --git a/juniper/src/types/utilities.rs b/juniper/src/types/utilities.rs index 2ed03b1f..f1be0792 100644 --- a/juniper/src/types/utilities.rs +++ b/juniper/src/types/utilities.rs @@ -25,6 +25,7 @@ where } } TypeType::List(ref inner, expected_size) => match *arg_value { + InputValue::Null | InputValue::Variable(_) => true, InputValue::List(ref items) => { if let Some(expected) = expected_size { if items.len() != expected { @@ -71,7 +72,7 @@ where let mut remaining_required_fields = input_fields .iter() .filter_map(|f| { - if f.arg_type.is_non_null() { + if f.arg_type.is_non_null() && f.default_value.is_none() { Some(&f.name) } else { None diff --git a/juniper/src/validation/context.rs b/juniper/src/validation/context.rs index d90e7362..c96c0491 100644 --- a/juniper/src/validation/context.rs +++ b/juniper/src/validation/context.rs @@ -30,9 +30,9 @@ pub struct ValidatorContext<'a, S: Debug + 'a> { impl RuleError { #[doc(hidden)] - pub fn new(message: &str, locations: &[SourcePosition]) -> RuleError { - RuleError { - message: message.to_owned(), + pub fn new(message: &str, locations: &[SourcePosition]) -> Self { + Self { + message: message.into(), locations: locations.to_vec(), } } @@ -53,14 +53,15 @@ impl RuleError { impl fmt::Display for RuleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // this is fine since all `RuleError`s should have at least one source position + // This is fine since all `RuleError`s should have at least one source + // position. let locations = self .locations .iter() - .map(|location| format!("{}", location)) + .map(ToString::to_string) .collect::>() .join(", "); - write!(f, "{}. At {}", self.message, locations) + write!(f, "{}. At {locations}", self.message) } } diff --git a/juniper/src/validation/input_value.rs b/juniper/src/validation/input_value.rs index b6238172..780a0ab3 100644 --- a/juniper/src/validation/input_value.rs +++ b/juniper/src/validation/input_value.rs @@ -99,7 +99,7 @@ where var_name, var_pos, &path, - &format!(r#"Expected "{}", found null"#, meta_type), + format!(r#"Expected "{meta_type}", found null"#), )); } else { errors.append(&mut unify_value( @@ -121,10 +121,10 @@ where var_name, var_pos, &path, - &format!( - "Expected list of {} elements, found {} elements", - expected, - l.len() + format!( + "Expected list of {expected} elements, \ + found {} elements", + l.len(), ), )); } @@ -168,11 +168,11 @@ where var_name, var_pos, &path, - &format!( - "Expected input of type `{}`. Got: `{}`. \ + format!( + "Expected input of type `{}`. \ + Got: `{value}`. \ Details: {}", iom.name, - value, e.message(), ), )); @@ -205,10 +205,9 @@ where var_name, var_pos, path, - &format!( - "Expected input scalar `{}`. Got: `{}`. Details: {}", + format!( + "Expected input scalar `{}`. Got: `{value}`. Details: {}", meta.name, - value, e.message(), ), )]; @@ -219,13 +218,13 @@ where var_name, var_pos, path, - &format!(r#"Expected "{}", found list"#, meta.name), + format!(r#"Expected "{}", found list"#, meta.name), )), InputValue::Object(_) => errors.push(unification_error( var_name, var_pos, path, - &format!(r#"Expected "{}", found object"#, meta.name), + format!(r#"Expected "{}", found object"#, meta.name), )), _ => (), } @@ -244,27 +243,27 @@ where { let mut errors: Vec = vec![]; - match *value { + match value { // TODO: avoid this bad duplicate as_str() call. (value system refactor) - InputValue::Scalar(ref scalar) if scalar.as_str().is_some() => { + InputValue::Scalar(scalar) if scalar.as_str().is_some() => { if let Some(name) = scalar.as_str() { if !meta.values.iter().any(|ev| ev.name == *name) { errors.push(unification_error( var_name, var_pos, path, - &format!(r#"Invalid value for enum "{}""#, meta.name), + format!(r#"Invalid value for enum "{}""#, meta.name), )) } } } - InputValue::Enum(ref name) => { + InputValue::Enum(name) => { if !meta.values.iter().any(|ev| &ev.name == name) { errors.push(unification_error( var_name, var_pos, path, - &format!(r#"Invalid value for enum "{}""#, meta.name), + format!(r#"Invalid value for enum "{}""#, meta.name), )) } } @@ -272,7 +271,7 @@ where var_name, var_pos, path, - &format!(r#"Expected "{}", found not a string or enum"#, meta.name), + format!(r#"Expected "{}", found not a string or enum"#, meta.name), )), } errors @@ -318,7 +317,7 @@ where var_name, var_pos, &Path::ObjectField(&input_field.name, path), - &format!(r#"Expected "{}", found null"#, input_field.arg_type), + format!(r#"Expected "{}", found null"#, input_field.arg_type), )); } } @@ -336,7 +335,7 @@ where var_name, var_pos, path, - &format!(r#"Expected "{}", found not an object"#, meta.name), + format!(r#"Expected "{}", found not an object"#, meta.name), )); } errors @@ -349,17 +348,14 @@ where v.map_or(true, InputValue::is_null) } -fn unification_error<'a>( - var_name: &str, +fn unification_error( + var_name: impl fmt::Display, var_pos: &SourcePosition, - path: &Path<'a>, - message: &str, + path: &Path<'_>, + message: impl fmt::Display, ) -> RuleError { RuleError::new( - &format!( - r#"Variable "${}" got invalid value. {}{}."#, - var_name, path, message, - ), + &format!(r#"Variable "${var_name}" got invalid value. {path}{message}."#), &[*var_pos], ) } @@ -368,8 +364,8 @@ impl<'a> fmt::Display for Path<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Path::Root => write!(f, ""), - Path::ArrayElement(idx, prev) => write!(f, "{}In element #{}: ", prev, idx), - Path::ObjectField(name, prev) => write!(f, r#"{}In field "{}": "#, prev, name), + Path::ArrayElement(idx, prev) => write!(f, "{prev}In element #{idx}: "), + Path::ObjectField(name, prev) => write!(f, r#"{prev}In field "{name}": "#), } } } diff --git a/juniper/src/validation/rules/arguments_of_correct_type.rs b/juniper/src/validation/rules/arguments_of_correct_type.rs index 495ca4f7..2c7aadbe 100644 --- a/juniper/src/validation/rules/arguments_of_correct_type.rs +++ b/juniper/src/validation/rules/arguments_of_correct_type.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{ ast::{Directive, Field, InputValue}, parser::Spanning, @@ -6,13 +8,12 @@ use crate::{ validation::{ValidatorContext, Visitor}, value::ScalarValue, }; -use std::fmt::Debug; -pub struct ArgumentsOfCorrectType<'a, S: Debug + 'a> { +pub struct ArgumentsOfCorrectType<'a, S: fmt::Debug + 'a> { current_args: Option<&'a Vec>>, } -pub fn factory<'a, S: Debug>() -> ArgumentsOfCorrectType<'a, S> { +pub fn factory<'a, S: fmt::Debug>() -> ArgumentsOfCorrectType<'a, S> { ArgumentsOfCorrectType { current_args: None } } @@ -59,7 +60,7 @@ where if !is_valid_literal_value(ctx.schema, &meta_type, &arg_value.item) { ctx.report_error( - &error_message(arg_name.item, &format!("{}", argument_meta.arg_type)), + &error_message(arg_name.item, &argument_meta.arg_type), &[arg_value.start], ); } @@ -67,11 +68,8 @@ where } } -fn error_message(arg_name: &str, type_name: &str) -> String { - format!( - "Invalid value for argument \"{}\", expected type \"{}\"", - arg_name, type_name - ) +fn error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String { + format!("Invalid value for argument \"{arg_name}\", expected type \"{type_name}\"",) } #[cfg(test)] @@ -85,7 +83,7 @@ mod tests { }; #[test] - fn good_null_value() { + fn null_into_nullable_int() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" @@ -98,6 +96,20 @@ mod tests { ); } + #[test] + fn null_into_nullable_list() { + expect_passes_rule::<_, _, DefaultScalarValue>( + factory, + r#" + { + complicatedArgs { + stringListArgField(stringListArg: null) + } + } + "#, + ); + } + #[test] fn null_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( @@ -116,6 +128,24 @@ mod tests { ); } + #[test] + fn null_into_list() { + expect_fails_rule::<_, _, DefaultScalarValue>( + factory, + r#" + { + complicatedArgs { + nonNullStringListArgField(nonNullStringListArg: null) + } + } + "#, + &[RuleError::new( + &error_message("nonNullStringListArg", "[String!]!"), + &[SourcePosition::new(111, 3, 64)], + )], + ); + } + #[test] fn good_int_value() { expect_passes_rule::<_, _, DefaultScalarValue>( diff --git a/juniper/src/validation/rules/default_values_of_correct_type.rs b/juniper/src/validation/rules/default_values_of_correct_type.rs index 8ef4f98b..2d15f854 100644 --- a/juniper/src/validation/rules/default_values_of_correct_type.rs +++ b/juniper/src/validation/rules/default_values_of_correct_type.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{ ast::VariableDefinition, parser::Spanning, @@ -29,7 +31,7 @@ where { if var_def.var_type.item.is_non_null() { ctx.report_error( - &non_null_error_message(var_name.item, &format!("{}", var_def.var_type.item)), + &non_null_error_message(var_name.item, &var_def.var_type.item), &[*start], ) } else { @@ -37,7 +39,7 @@ where if !is_valid_literal_value(ctx.schema, &meta_type, var_value) { ctx.report_error( - &type_error_message(var_name.item, &format!("{}", var_def.var_type.item)), + &type_error_message(var_name.item, &var_def.var_type.item), &[*start], ); } @@ -46,17 +48,14 @@ where } } -fn type_error_message(arg_name: &str, type_name: &str) -> String { - format!( - "Invalid default value for argument \"{}\", expected type \"{}\"", - arg_name, type_name - ) +fn type_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String { + format!("Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\"") } -fn non_null_error_message(arg_name: &str, type_name: &str) -> String { +fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( - "Argument \"{}\" has type \"{}\" and is not nullable, so it can't have a default value", - arg_name, type_name + "Argument \"{arg_name}\" has type \"{type_name}\" and is not nullable, \ + so it can't have a default value", ) } diff --git a/juniper/src/validation/rules/fields_on_correct_type.rs b/juniper/src/validation/rules/fields_on_correct_type.rs index ad90cb25..b195df45 100644 --- a/juniper/src/validation/rules/fields_on_correct_type.rs +++ b/juniper/src/validation/rules/fields_on_correct_type.rs @@ -69,7 +69,7 @@ where } fn error_message(field: &str, type_name: &str) -> String { - format!(r#"Unknown field "{}" on type "{}""#, field, type_name) + format!(r#"Unknown field "{field}" on type "{type_name}""#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/fragments_on_composite_types.rs b/juniper/src/validation/rules/fragments_on_composite_types.rs index 1d2ca75b..927f46de 100644 --- a/juniper/src/validation/rules/fragments_on_composite_types.rs +++ b/juniper/src/validation/rules/fragments_on_composite_types.rs @@ -59,15 +59,9 @@ where fn error_message(fragment_name: Option<&str>, on_type: &str) -> String { if let Some(name) = fragment_name { - format!( - r#"Fragment "{}" cannot condition non composite type "{}"#, - name, on_type - ) + format!(r#"Fragment "{name}" cannot condition non composite type "{on_type}"#) } else { - format!( - r#"Fragment cannot condition on non composite type "{}""#, - on_type - ) + format!(r#"Fragment cannot condition on non composite type "{on_type}""#) } } diff --git a/juniper/src/validation/rules/known_argument_names.rs b/juniper/src/validation/rules/known_argument_names.rs index 6e579977..d652caf3 100644 --- a/juniper/src/validation/rules/known_argument_names.rs +++ b/juniper/src/validation/rules/known_argument_names.rs @@ -91,17 +91,11 @@ where } fn field_error_message(arg_name: &str, field_name: &str, type_name: &str) -> String { - format!( - r#"Unknown argument "{}" on field "{}" of type "{}""#, - arg_name, field_name, type_name - ) + format!(r#"Unknown argument "{arg_name}" on field "{field_name}" of type "{type_name}""#) } fn directive_error_message(arg_name: &str, directive_name: &str) -> String { - format!( - r#"Unknown argument "{}" on directive "{}""#, - arg_name, directive_name - ) + format!(r#"Unknown argument "{arg_name}" on directive "{directive_name}""#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/known_directives.rs b/juniper/src/validation/rules/known_directives.rs index 291e17db..9a2b9f5b 100644 --- a/juniper/src/validation/rules/known_directives.rs +++ b/juniper/src/validation/rules/known_directives.rs @@ -154,14 +154,11 @@ where } fn unknown_error_message(directive_name: &str) -> String { - format!(r#"Unknown directive "{}""#, directive_name) + format!(r#"Unknown directive "{directive_name}""#) } fn misplaced_error_message(directive_name: &str, location: &DirectiveLocation) -> String { - format!( - r#"Directive "{}" may not be used on {}"#, - directive_name, location - ) + format!(r#"Directive "{directive_name}" may not be used on {location}"#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/known_fragment_names.rs b/juniper/src/validation/rules/known_fragment_names.rs index 8795de4a..ac4b7b19 100644 --- a/juniper/src/validation/rules/known_fragment_names.rs +++ b/juniper/src/validation/rules/known_fragment_names.rs @@ -28,7 +28,7 @@ where } fn error_message(frag_name: &str) -> String { - format!(r#"Unknown fragment: "{}""#, frag_name) + format!(r#"Unknown fragment: "{frag_name}""#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/known_type_names.rs b/juniper/src/validation/rules/known_type_names.rs index b5b2c936..4f2dffc2 100644 --- a/juniper/src/validation/rules/known_type_names.rs +++ b/juniper/src/validation/rules/known_type_names.rs @@ -56,7 +56,7 @@ fn validate_type<'a, S: Debug>( } fn error_message(type_name: &str) -> String { - format!(r#"Unknown type "{}""#, type_name) + format!(r#"Unknown type "{type_name}""#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/no_fragment_cycles.rs b/juniper/src/validation/rules/no_fragment_cycles.rs index c5489e08..0845f820 100644 --- a/juniper/src/validation/rules/no_fragment_cycles.rs +++ b/juniper/src/validation/rules/no_fragment_cycles.rs @@ -7,19 +7,6 @@ use crate::{ value::ScalarValue, }; -pub struct NoFragmentCycles<'a> { - current_fragment: Option<&'a str>, - spreads: HashMap<&'a str, Vec>>, - fragment_order: Vec<&'a str>, -} - -struct CycleDetector<'a> { - visited: HashSet<&'a str>, - spreads: &'a HashMap<&'a str, Vec>>, - path_indices: HashMap<&'a str, usize>, - errors: Vec, -} - pub fn factory<'a>() -> NoFragmentCycles<'a> { NoFragmentCycles { current_fragment: None, @@ -28,6 +15,12 @@ pub fn factory<'a>() -> NoFragmentCycles<'a> { } } +pub struct NoFragmentCycles<'a> { + current_fragment: Option<&'a str>, + spreads: HashMap<&'a str, Vec>>, + fragment_order: Vec<&'a str>, +} + impl<'a, S> Visitor<'a, S> for NoFragmentCycles<'a> where S: ScalarValue, @@ -38,14 +31,12 @@ where let mut detector = CycleDetector { visited: HashSet::new(), spreads: &self.spreads, - path_indices: HashMap::new(), errors: Vec::new(), }; for frag in &self.fragment_order { if !detector.visited.contains(frag) { - let mut path = Vec::new(); - detector.detect_from(frag, &mut path); + detector.detect_from(frag); } } @@ -91,19 +82,46 @@ where } } +type CycleDetectorState<'a> = (&'a str, Vec<&'a Spanning<&'a str>>, HashMap<&'a str, usize>); + +struct CycleDetector<'a> { + visited: HashSet<&'a str>, + spreads: &'a HashMap<&'a str, Vec>>, + errors: Vec, +} + impl<'a> CycleDetector<'a> { - fn detect_from(&mut self, from: &'a str, path: &mut Vec<&'a Spanning<&'a str>>) { + fn detect_from(&mut self, from: &'a str) { + let mut to_visit = Vec::new(); + to_visit.push((from, Vec::new(), HashMap::new())); + + while let Some((from, path, path_indices)) = to_visit.pop() { + to_visit.extend(self.detect_from_inner(from, path, path_indices)); + } + } + + /// This function should be called only inside [`Self::detect_from()`], as + /// it's a recursive function using heap instead of a stack. So, instead of + /// the recursive call, we return a [`Vec`] that is visited inside + /// [`Self::detect_from()`]. + fn detect_from_inner( + &mut self, + from: &'a str, + path: Vec<&'a Spanning<&'a str>>, + mut path_indices: HashMap<&'a str, usize>, + ) -> Vec> { self.visited.insert(from); if !self.spreads.contains_key(from) { - return; + return Vec::new(); } - self.path_indices.insert(from, path.len()); + path_indices.insert(from, path.len()); + let mut to_visit = Vec::new(); for node in &self.spreads[from] { - let name = &node.item; - let index = self.path_indices.get(name).cloned(); + let name = node.item; + let index = path_indices.get(name).cloned(); if let Some(index) = index { let err_pos = if index < path.len() { @@ -114,19 +132,19 @@ impl<'a> CycleDetector<'a> { self.errors .push(RuleError::new(&error_message(name), &[err_pos.start])); - } else if !self.visited.contains(name) { + } else { + let mut path = path.clone(); path.push(node); - self.detect_from(name, path); - path.pop(); + to_visit.push((name, path, path_indices.clone())); } } - self.path_indices.remove(from); + to_visit } } fn error_message(frag_name: &str) -> String { - format!(r#"Cannot spread fragment "{}""#, frag_name) + format!(r#"Cannot spread fragment "{frag_name}""#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/no_undefined_variables.rs b/juniper/src/validation/rules/no_undefined_variables.rs index 8f13f191..6e382b23 100644 --- a/juniper/src/validation/rules/no_undefined_variables.rs +++ b/juniper/src/validation/rules/no_undefined_variables.rs @@ -12,13 +12,6 @@ pub enum Scope<'a> { Fragment(&'a str), } -pub struct NoUndefinedVariables<'a> { - defined_variables: HashMap, (SourcePosition, HashSet<&'a str>)>, - used_variables: HashMap, Vec>>, - current_scope: Option>, - spreads: HashMap, Vec<&'a str>>, -} - pub fn factory<'a>() -> NoUndefinedVariables<'a> { NoUndefinedVariables { defined_variables: HashMap::new(), @@ -28,6 +21,13 @@ pub fn factory<'a>() -> NoUndefinedVariables<'a> { } } +pub struct NoUndefinedVariables<'a> { + defined_variables: HashMap, (SourcePosition, HashSet<&'a str>)>, + used_variables: HashMap, Vec>>, + current_scope: Option>, + spreads: HashMap, Vec<&'a str>>, +} + impl<'a> NoUndefinedVariables<'a> { fn find_undef_vars( &'a self, @@ -36,8 +36,34 @@ impl<'a> NoUndefinedVariables<'a> { unused: &mut Vec<&'a Spanning<&'a str>>, visited: &mut HashSet>, ) { + let mut to_visit = Vec::new(); + if let Some(spreads) = self.find_undef_vars_inner(scope, defined, unused, visited) { + to_visit.push(spreads); + } + while let Some(spreads) = to_visit.pop() { + for spread in spreads { + if let Some(spreads) = + self.find_undef_vars_inner(&Scope::Fragment(spread), defined, unused, visited) + { + to_visit.push(spreads); + } + } + } + } + + /// This function should be called only inside [`Self::find_undef_vars()`], + /// as it's a recursive function using heap instead of a stack. So, instead + /// of the recursive call, we return a [`Vec`] that is visited inside + /// [`Self::find_undef_vars()`]. + fn find_undef_vars_inner( + &'a self, + scope: &Scope<'a>, + defined: &HashSet<&'a str>, + unused: &mut Vec<&'a Spanning<&'a str>>, + visited: &mut HashSet>, + ) -> Option<&'a Vec<&'a str>> { if visited.contains(scope) { - return; + return None; } visited.insert(scope.clone()); @@ -50,11 +76,7 @@ impl<'a> NoUndefinedVariables<'a> { } } - if let Some(spreads) = self.spreads.get(scope) { - for spread in spreads { - self.find_undef_vars(&Scope::Fragment(spread), defined, unused, visited); - } - } + self.spreads.get(scope) } } @@ -151,12 +173,9 @@ where fn error_message(var_name: &str, op_name: Option<&str>) -> String { if let Some(op_name) = op_name { - format!( - r#"Variable "${}" is not defined by operation "{}""#, - var_name, op_name - ) + format!(r#"Variable "${var_name}" is not defined by operation "{op_name}""#) } else { - format!(r#"Variable "${}" is not defined"#, var_name) + format!(r#"Variable "${var_name}" is not defined"#) } } diff --git a/juniper/src/validation/rules/no_unused_fragments.rs b/juniper/src/validation/rules/no_unused_fragments.rs index 97b2bcf8..bb1e9fbb 100644 --- a/juniper/src/validation/rules/no_unused_fragments.rs +++ b/juniper/src/validation/rules/no_unused_fragments.rs @@ -7,18 +7,12 @@ use crate::{ value::ScalarValue, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Scope<'a> { Operation(Option<&'a str>), Fragment(&'a str), } -pub struct NoUnusedFragments<'a> { - spreads: HashMap, Vec<&'a str>>, - defined_fragments: HashSet>, - current_scope: Option>, -} - pub fn factory<'a>() -> NoUnusedFragments<'a> { NoUnusedFragments { spreads: HashMap::new(), @@ -27,21 +21,42 @@ pub fn factory<'a>() -> NoUnusedFragments<'a> { } } +pub struct NoUnusedFragments<'a> { + spreads: HashMap, Vec<&'a str>>, + defined_fragments: HashSet>, + current_scope: Option>, +} + impl<'a> NoUnusedFragments<'a> { - fn find_reachable_fragments(&self, from: &Scope<'a>, result: &mut HashSet<&'a str>) { - if let Scope::Fragment(name) = *from { + fn find_reachable_fragments(&'a self, from: Scope<'a>, result: &mut HashSet<&'a str>) { + let mut to_visit = Vec::new(); + to_visit.push(from); + + while let Some(from) = to_visit.pop() { + if let Some(next) = self.find_reachable_fragments_inner(from, result) { + to_visit.extend(next.iter().map(|s| Scope::Fragment(s))); + } + } + } + + /// This function should be called only inside + /// [`Self::find_reachable_fragments()`], as it's a recursive function using + /// heap instead of a stack. So, instead of the recursive call, we return a + /// [`Vec`] that is visited inside [`Self::find_reachable_fragments()`]. + fn find_reachable_fragments_inner( + &'a self, + from: Scope<'a>, + result: &mut HashSet<&'a str>, + ) -> Option<&'a Vec<&'a str>> { + if let Scope::Fragment(name) = from { if result.contains(name) { - return; + return None; } else { result.insert(name); } } - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.find_reachable_fragments(&Scope::Fragment(spread), result) - } - } + self.spreads.get(&from) } } @@ -59,7 +74,7 @@ where }) = *def { let op_name = name.as_ref().map(|s| s.item); - self.find_reachable_fragments(&Scope::Operation(op_name), &mut reachable); + self.find_reachable_fragments(Scope::Operation(op_name), &mut reachable); } } @@ -96,7 +111,7 @@ where ) { if let Some(ref scope) = self.current_scope { self.spreads - .entry(scope.clone()) + .entry(*scope) .or_insert_with(Vec::new) .push(spread.item.name.item); } @@ -104,7 +119,7 @@ where } fn error_message(frag_name: &str) -> String { - format!(r#"Fragment "{}" is never used"#, frag_name) + format!(r#"Fragment "{frag_name}" is never used"#) } #[cfg(test)] diff --git a/juniper/src/validation/rules/no_unused_variables.rs b/juniper/src/validation/rules/no_unused_variables.rs index 35e5f933..81b7dfb3 100644 --- a/juniper/src/validation/rules/no_unused_variables.rs +++ b/juniper/src/validation/rules/no_unused_variables.rs @@ -12,13 +12,6 @@ pub enum Scope<'a> { Fragment(&'a str), } -pub struct NoUnusedVariables<'a> { - defined_variables: HashMap, HashSet<&'a Spanning<&'a str>>>, - used_variables: HashMap, Vec<&'a str>>, - current_scope: Option>, - spreads: HashMap, Vec<&'a str>>, -} - pub fn factory<'a>() -> NoUnusedVariables<'a> { NoUnusedVariables { defined_variables: HashMap::new(), @@ -28,16 +21,49 @@ pub fn factory<'a>() -> NoUnusedVariables<'a> { } } +pub struct NoUnusedVariables<'a> { + defined_variables: HashMap, HashSet<&'a Spanning<&'a str>>>, + used_variables: HashMap, Vec<&'a str>>, + current_scope: Option>, + spreads: HashMap, Vec<&'a str>>, +} + impl<'a> NoUnusedVariables<'a> { fn find_used_vars( - &self, + &'a self, from: &Scope<'a>, defined: &HashSet<&'a str>, used: &mut HashSet<&'a str>, visited: &mut HashSet>, ) { + let mut to_visit = Vec::new(); + if let Some(spreads) = self.find_used_vars_inner(from, defined, used, visited) { + to_visit.push(spreads); + } + while let Some(spreads) = to_visit.pop() { + for spread in spreads { + if let Some(spreads) = + self.find_used_vars_inner(&Scope::Fragment(spread), defined, used, visited) + { + to_visit.push(spreads); + } + } + } + } + + /// This function should be called only inside [`Self::find_used_vars()`], + /// as it's a recursive function using heap instead of a stack. So, instead + /// of the recursive call, we return a [`Vec`] that is visited inside + /// [`Self::find_used_vars()`]. + fn find_used_vars_inner( + &'a self, + from: &Scope<'a>, + defined: &HashSet<&'a str>, + used: &mut HashSet<&'a str>, + visited: &mut HashSet>, + ) -> Option<&'a Vec<&'a str>> { if visited.contains(from) { - return; + return None; } visited.insert(from.clone()); @@ -50,11 +76,7 @@ impl<'a> NoUnusedVariables<'a> { } } - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.find_used_vars(&Scope::Fragment(spread), defined, used, visited); - } - } + self.spreads.get(from) } } @@ -142,12 +164,9 @@ where fn error_message(var_name: &str, op_name: Option<&str>) -> String { if let Some(op_name) = op_name { - format!( - r#"Variable "${}" is not used by operation "{}""#, - var_name, op_name - ) + format!(r#"Variable "${var_name}" is not used by operation "{op_name}""#) } else { - format!(r#"Variable "${}" is not used"#, var_name) + format!(r#"Variable "${var_name}" is not used"#) } } diff --git a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs index 1b13f393..efacd1ce 100644 --- a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -274,30 +274,61 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { ) where S: ScalarValue, { - let fragment = match self.named_fragments.get(fragment_name) { - Some(f) => f, - None => return, - }; + let mut to_check = Vec::new(); + if let Some(fragments) = self.collect_conflicts_between_fields_and_fragment_inner( + conflicts, + field_map, + fragment_name, + mutually_exclusive, + ctx, + ) { + to_check.push((fragment_name, fragments)) + } + + while let Some((fragment_name, fragment_names2)) = to_check.pop() { + for fragment_name2 in fragment_names2 { + // Early return on fragment recursion, as it makes no sense. + // Fragment recursions are prevented by `no_fragment_cycles` validator. + if fragment_name == fragment_name2 { + return; + } + if let Some(fragments) = self.collect_conflicts_between_fields_and_fragment_inner( + conflicts, + field_map, + fragment_name2, + mutually_exclusive, + ctx, + ) { + to_check.push((fragment_name2, fragments)); + }; + } + } + } + + /// This function should be called only inside + /// [`Self::collect_conflicts_between_fields_and_fragment()`], as it's a + /// recursive function using heap instead of a stack. So, instead of the + /// recursive call, we return a [`Vec`] that is visited inside + /// [`Self::collect_conflicts_between_fields_and_fragment()`]. + fn collect_conflicts_between_fields_and_fragment_inner( + &self, + conflicts: &mut Vec, + field_map: &AstAndDefCollection<'a, S>, + fragment_name: &str, + mutually_exclusive: bool, + ctx: &ValidatorContext<'a, S>, + ) -> Option> + where + S: ScalarValue, + { + let fragment = self.named_fragments.get(fragment_name)?; let (field_map2, fragment_names2) = self.get_referenced_fields_and_fragment_names(fragment, ctx); self.collect_conflicts_between(conflicts, mutually_exclusive, field_map, &field_map2, ctx); - for fragment_name2 in fragment_names2 { - // Early return on fragment recursion, as it makes no sense. - // Fragment recursions are prevented by `no_fragment_cycles` validator. - if fragment_name == fragment_name2 { - return; - } - self.collect_conflicts_between_fields_and_fragment( - conflicts, - field_map, - fragment_name2, - mutually_exclusive, - ctx, - ); - } + Some(fragment_names2) } fn collect_conflicts_between( @@ -376,10 +407,9 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { if name1 != name2 { return Some(Conflict( ConflictReason( - response_name.to_owned(), + response_name.into(), ConflictReasonMessage::Message(format!( - "{} and {} are different fields", - name1, name2 + "{name1} and {name2} are different fields", )), ), vec![ast1.start], @@ -390,8 +420,8 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { if !self.is_same_arguments(&ast1.item.arguments, &ast2.item.arguments) { return Some(Conflict( ConflictReason( - response_name.to_owned(), - ConflictReasonMessage::Message("they have differing arguments".to_owned()), + response_name.into(), + ConflictReasonMessage::Message("they have differing arguments".into()), ), vec![ast1.start], vec![ast2.start], @@ -406,10 +436,9 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { if self.is_type_conflict(ctx, t1, t2) { return Some(Conflict( ConflictReason( - response_name.to_owned(), + response_name.into(), ConflictReasonMessage::Message(format!( - "they return conflicting types {} and {}", - t1, t2 + "they return conflicting types {t1} and {t2}", )), ), vec![ast1.start], @@ -513,7 +542,7 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { Some(Conflict( ConflictReason( - response_name.to_owned(), + response_name.into(), ConflictReasonMessage::Nested(conflicts.iter().map(|c| c.0.clone()).collect()), ), vec![*pos1] @@ -722,10 +751,8 @@ where fn error_message(reason_name: &str, reason: &ConflictReasonMessage) -> String { let suffix = "Use different aliases on the fields to fetch both if this was intentional"; format!( - r#"Fields "{}" conflict because {}. {}"#, - reason_name, + r#"Fields "{reason_name}" conflict because {}. {suffix}"#, format_reason(reason), - suffix ) } @@ -736,9 +763,8 @@ fn format_reason(reason: &ConflictReasonMessage) -> String { .iter() .map(|&ConflictReason(ref name, ref subreason)| { format!( - r#"subfields "{}" conflict because {}"#, - name, - format_reason(subreason) + r#"subfields "{name}" conflict because {}"#, + format_reason(subreason), ) }) .collect::>() @@ -872,7 +898,7 @@ mod tests { &[RuleError::new( &error_message( "fido", - &Message("name and nickname are different fields".to_owned()), + &Message("name and nickname are different fields".into()), ), &[ SourcePosition::new(78, 2, 12), @@ -912,7 +938,7 @@ mod tests { &[RuleError::new( &error_message( "name", - &Message("nickname and name are different fields".to_owned()), + &Message("nickname and name are different fields".into()), ), &[ SourcePosition::new(71, 2, 12), @@ -935,7 +961,7 @@ mod tests { &[RuleError::new( &error_message( "doesKnowCommand", - &Message("they have differing arguments".to_owned()), + &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), @@ -958,7 +984,7 @@ mod tests { &[RuleError::new( &error_message( "doesKnowCommand", - &Message("they have differing arguments".to_owned()), + &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), @@ -981,7 +1007,7 @@ mod tests { &[RuleError::new( &error_message( "doesKnowCommand", - &Message("they have differing arguments".to_owned()), + &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), @@ -1025,10 +1051,7 @@ mod tests { } "#, &[RuleError::new( - &error_message( - "x", - &Message("name and barks are different fields".to_owned()), - ), + &error_message("x", &Message("name and barks are different fields".into())), &[ SourcePosition::new(101, 6, 12), SourcePosition::new(163, 9, 12), @@ -1066,10 +1089,7 @@ mod tests { "#, &[ RuleError::new( - &error_message( - "x", - &Message("name and barks are different fields".to_owned()), - ), + &error_message("x", &Message("name and barks are different fields".into())), &[ SourcePosition::new(235, 13, 14), SourcePosition::new(311, 17, 12), @@ -1078,7 +1098,7 @@ mod tests { RuleError::new( &error_message( "x", - &Message("name and nickname are different fields".to_owned()), + &Message("name and nickname are different fields".into()), ), &[ SourcePosition::new(235, 13, 14), @@ -1088,7 +1108,7 @@ mod tests { RuleError::new( &error_message( "x", - &Message("barks and nickname are different fields".to_owned()), + &Message("barks and nickname are different fields".into()), ), &[ SourcePosition::new(311, 17, 12), @@ -1117,8 +1137,8 @@ mod tests { &error_message( "dog", &Nested(vec![ConflictReason( - "x".to_owned(), - Message("name and barks are different fields".to_owned()), + "x".into(), + Message("name and barks are different fields".into()), )]), ), &[ @@ -1152,12 +1172,12 @@ mod tests { "dog", &Nested(vec![ ConflictReason( - "x".to_owned(), - Message("barks and nickname are different fields".to_owned()), + "x".into(), + Message("barks and nickname are different fields".into()), ), ConflictReason( - "y".to_owned(), - Message("name and barkVolume are different fields".to_owned()), + "y".into(), + Message("name and barkVolume are different fields".into()), ), ]), ), @@ -1195,10 +1215,10 @@ mod tests { &error_message( "human", &Nested(vec![ConflictReason( - "relatives".to_owned(), + "relatives".into(), Nested(vec![ConflictReason( - "x".to_owned(), - Message("name and iq are different fields".to_owned()), + "x".into(), + Message("name and iq are different fields".into()), )]), )]), ), @@ -1239,8 +1259,8 @@ mod tests { &error_message( "relatives", &Nested(vec![ConflictReason( - "x".to_owned(), - Message("iq and name are different fields".to_owned()), + "x".into(), + Message("iq and name are different fields".into()), )]), ), &[ @@ -1286,8 +1306,8 @@ mod tests { &error_message( "relatives", &Nested(vec![ConflictReason( - "x".to_owned(), - Message("iq and name are different fields".to_owned()), + "x".into(), + Message("iq and name are different fields".into()), )]), ), &[ @@ -1333,12 +1353,12 @@ mod tests { "dog", &Nested(vec![ ConflictReason( - "x".to_owned(), - Message("name and barks are different fields".to_owned()), + "x".into(), + Message("name and barks are different fields".into()), ), ConflictReason( - "y".to_owned(), - Message("barkVolume and nickname are different fields".to_owned()), + "y".into(), + Message("barkVolume and nickname are different fields".into()), ), ]), ), @@ -1794,7 +1814,7 @@ mod tests { &[RuleError::new( &error_message( "scalar", - &Message("they return conflicting types Int and String!".to_owned()), + &Message("they return conflicting types Int and String!".into()), ), &[ SourcePosition::new(88, 4, 18), @@ -1858,7 +1878,7 @@ mod tests { &[RuleError::new( &error_message( "scalar", - &Message("they return conflicting types Int and String".to_owned()), + &Message("they return conflicting types Int and String".into()), ), &[ SourcePosition::new(89, 4, 18), @@ -1922,8 +1942,8 @@ mod tests { &error_message( "other", &Nested(vec![ConflictReason( - "otherField".to_owned(), - Message("otherField and unrelatedField are different fields".to_owned()), + "otherField".into(), + Message("otherField and unrelatedField are different fields".into()), )]), ), &[ @@ -1957,7 +1977,7 @@ mod tests { &[RuleError::new( &error_message( "scalar", - &Message("they return conflicting types String! and String".to_owned()), + &Message("they return conflicting types String! and String".into()), ), &[ SourcePosition::new(100, 4, 18), @@ -1992,7 +2012,7 @@ mod tests { &[RuleError::new( &error_message( "box", - &Message("they return conflicting types [StringBox] and StringBox".to_owned()), + &Message("they return conflicting types [StringBox] and StringBox".into()), ), &[ SourcePosition::new(89, 4, 18), @@ -2024,7 +2044,7 @@ mod tests { &[RuleError::new( &error_message( "box", - &Message("they return conflicting types StringBox and [StringBox]".to_owned()), + &Message("they return conflicting types StringBox and [StringBox]".into()), ), &[ SourcePosition::new(89, 4, 18), @@ -2060,7 +2080,7 @@ mod tests { &[RuleError::new( &error_message( "val", - &Message("scalar and unrelatedField are different fields".to_owned()), + &Message("scalar and unrelatedField are different fields".into()), ), &[ SourcePosition::new(126, 5, 20), @@ -2096,8 +2116,8 @@ mod tests { &error_message( "box", &Nested(vec![ConflictReason( - "scalar".to_owned(), - Message("they return conflicting types String and Int".to_owned()), + "scalar".into(), + Message("they return conflicting types String and Int".into()), )]), ), &[ @@ -2227,10 +2247,10 @@ mod tests { &error_message( "edges", &Nested(vec![ConflictReason( - "node".to_owned(), + "node".into(), Nested(vec![ConflictReason( - "id".to_owned(), - Message("name and id are different fields".to_owned()), + "id".into(), + Message("name and id are different fields".into()), )]), )]), ), @@ -2278,7 +2298,7 @@ mod tests { #[test] fn error_message_contains_hint_for_alias_conflict() { assert_eq!( - &error_message("x", &Message("a and b are different fields".to_owned())), + &error_message("x", &Message("a and b are different fields".into())), "Fields \"x\" conflict because a and b are different fields. Use \ different aliases on the fields to fetch both if this \ was intentional" diff --git a/juniper/src/validation/rules/possible_fragment_spreads.rs b/juniper/src/validation/rules/possible_fragment_spreads.rs index ceb308c9..c9e11f6f 100644 --- a/juniper/src/validation/rules/possible_fragment_spreads.rs +++ b/juniper/src/validation/rules/possible_fragment_spreads.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use crate::{ ast::{Definition, Document, FragmentSpread, InlineFragment}, + meta::InterfaceMeta, parser::Spanning, schema::meta::MetaType, validation::{ValidatorContext, Visitor}, @@ -45,6 +46,23 @@ where .as_ref() .and_then(|s| ctx.schema.concrete_type_by_name(s.item)), ) { + // Even if there is no object type in the overlap of interfaces + // implementers, it's OK to spread in case `frag_type` implements + // `parent_type`. + // https://spec.graphql.org/October2021#sel-JALVFJNRDABABqDy5B + if let MetaType::Interface(InterfaceMeta { + interface_names, .. + }) = frag_type + { + let implements_parent = parent_type + .name() + .map(|parent| interface_names.iter().any(|i| i == parent)) + .unwrap_or_default(); + if implements_parent { + return; + } + } + if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( @@ -67,6 +85,23 @@ where ctx.parent_type(), self.fragment_types.get(spread.item.name.item), ) { + // Even if there is no object type in the overlap of interfaces + // implementers, it's OK to spread in case `frag_type` implements + // `parent_type`. + // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B + if let MetaType::Interface(InterfaceMeta { + interface_names, .. + }) = frag_type + { + let implements_parent = parent_type + .name() + .map(|parent| interface_names.iter().any(|i| i == parent)) + .unwrap_or_default(); + if implements_parent { + return; + } + } + if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( @@ -84,15 +119,13 @@ where fn error_message(frag_name: Option<&str>, parent_type_name: &str, frag_type: &str) -> String { if let Some(frag_name) = frag_name { format!( - "Fragment \"{}\" cannot be spread here as objects of type \ - \"{}\" can never be of type \"{}\"", - frag_name, parent_type_name, frag_type + "Fragment \"{frag_name}\" cannot be spread here as objects of type \ + \"{parent_type_name}\" can never be of type \"{frag_type}\"", ) } else { format!( - "Fragment cannot be spread here as objects of type \"{}\" \ - can never be of type \"{}\"", - parent_type_name, frag_type + "Fragment cannot be spread here as objects of type \ + \"{parent_type_name}\" can never be of type \"{frag_type}\"", ) } } @@ -226,6 +259,27 @@ mod tests { ); } + #[test] + fn no_object_overlap_but_implements_parent() { + expect_passes_rule::<_, _, DefaultScalarValue>( + factory, + r#" + fragment beingFragment on Being { ...unpopulatedFragment } + fragment unpopulatedFragment on Unpopulated { name } + "#, + ); + } + + #[test] + fn no_object_overlap_but_implements_parent_inline() { + expect_passes_rule::<_, _, DefaultScalarValue>( + factory, + r#" + fragment beingFragment on Being { ...on Unpopulated { name } } + "#, + ); + } + #[test] fn different_object_into_object() { expect_fails_rule::<_, _, DefaultScalarValue>( diff --git a/juniper/src/validation/rules/provided_non_null_arguments.rs b/juniper/src/validation/rules/provided_non_null_arguments.rs index 7c2cb266..35672e18 100644 --- a/juniper/src/validation/rules/provided_non_null_arguments.rs +++ b/juniper/src/validation/rules/provided_non_null_arguments.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{ ast::{Directive, Field}, parser::Spanning, @@ -26,6 +28,7 @@ where { for meta_arg in meta_args { if meta_arg.arg_type.is_non_null() + && meta_arg.default_value.is_none() && field .item .arguments @@ -34,11 +37,7 @@ where .is_none() { ctx.report_error( - &field_error_message( - field_name, - &meta_arg.name, - &format!("{}", meta_arg.arg_type), - ), + &field_error_message(field_name, &meta_arg.name, &meta_arg.arg_type), &[field.start], ); } @@ -71,7 +70,7 @@ where &directive_error_message( directive_name, &meta_arg.name, - &format!("{}", meta_arg.arg_type), + &meta_arg.arg_type, ), &[directive.start], ); @@ -81,17 +80,23 @@ where } } -fn field_error_message(field_name: &str, arg_name: &str, type_name: &str) -> String { +fn field_error_message( + field_name: impl fmt::Display, + arg_name: impl fmt::Display, + type_name: impl fmt::Display, +) -> String { format!( - r#"Field "{}" argument "{}" of type "{}" is required but not provided"#, - field_name, arg_name, type_name + r#"Field "{field_name}" argument "{arg_name}" of type "{type_name}" is required but not provided"#, ) } -fn directive_error_message(directive_name: &str, arg_name: &str, type_name: &str) -> String { +fn directive_error_message( + directive_name: impl fmt::Display, + arg_name: impl fmt::Display, + type_name: impl fmt::Display, +) -> String { format!( - r#"Directive "@{}" argument "{}" of type "{}" is required but not provided"#, - directive_name, arg_name, type_name + r#"Directive "@{directive_name}" argument "{arg_name}" of type "{type_name}" is required but not provided"#, ) } diff --git a/juniper/src/validation/rules/scalar_leafs.rs b/juniper/src/validation/rules/scalar_leafs.rs index fe5ac777..00a92dad 100644 --- a/juniper/src/validation/rules/scalar_leafs.rs +++ b/juniper/src/validation/rules/scalar_leafs.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{ ast::Field, parser::Spanning, @@ -23,11 +25,11 @@ where { match (field_type.is_leaf(), &field.item.selection_set) { (true, &Some(_)) => Some(RuleError::new( - &no_allowed_error_message(field_name, &format!("{}", field_type_literal)), + &no_allowed_error_message(field_name, field_type_literal), &[field.start], )), (false, &None) => Some(RuleError::new( - &required_error_message(field_name, &format!("{}", field_type_literal)), + &required_error_message(field_name, field_type_literal), &[field.start], )), _ => None, @@ -42,17 +44,15 @@ where } } -fn no_allowed_error_message(field_name: &str, type_name: &str) -> String { +fn no_allowed_error_message(field_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( - r#"Field "{}" must not have a selection since type {} has no subfields"#, - field_name, type_name + r#"Field "{field_name}" must not have a selection since type {type_name} has no subfields"#, ) } -fn required_error_message(field_name: &str, type_name: &str) -> String { +fn required_error_message(field_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( - r#"Field "{}" of type "{}" must have a selection of subfields. Did you mean "{} {{ ... }}"?"#, - field_name, type_name, field_name + r#"Field "{field_name}" of type "{type_name}" must have a selection of subfields. Did you mean "{field_name} {{ ... }}"?"#, ) } diff --git a/juniper/src/validation/rules/unique_argument_names.rs b/juniper/src/validation/rules/unique_argument_names.rs index 0b6ae6d6..697e0a27 100644 --- a/juniper/src/validation/rules/unique_argument_names.rs +++ b/juniper/src/validation/rules/unique_argument_names.rs @@ -46,7 +46,7 @@ where } fn error_message(arg_name: &str) -> String { - format!("There can only be one argument named \"{}\"", arg_name) + format!("There can only be one argument named \"{arg_name}\"") } #[cfg(test)] diff --git a/juniper/src/validation/rules/unique_fragment_names.rs b/juniper/src/validation/rules/unique_fragment_names.rs index 2bcbb440..f54e46d1 100644 --- a/juniper/src/validation/rules/unique_fragment_names.rs +++ b/juniper/src/validation/rules/unique_fragment_names.rs @@ -41,7 +41,7 @@ where } fn duplicate_message(frag_name: &str) -> String { - format!("There can only be one fragment named {}", frag_name) + format!("There can only be one fragment named {frag_name}") } #[cfg(test)] diff --git a/juniper/src/validation/rules/unique_input_field_names.rs b/juniper/src/validation/rules/unique_input_field_names.rs index db507502..7108c767 100644 --- a/juniper/src/validation/rules/unique_input_field_names.rs +++ b/juniper/src/validation/rules/unique_input_field_names.rs @@ -53,7 +53,7 @@ where type SpannedObject<'a, S> = Spanning<&'a Vec<(Spanning, Spanning>)>>; fn error_message(field_name: &str) -> String { - format!("There can only be one input field named \"{}\"", field_name) + format!("There can only be one input field named \"{field_name}\"") } #[cfg(test)] diff --git a/juniper/src/validation/rules/unique_operation_names.rs b/juniper/src/validation/rules/unique_operation_names.rs index 4d7e56f1..8f693696 100644 --- a/juniper/src/validation/rules/unique_operation_names.rs +++ b/juniper/src/validation/rules/unique_operation_names.rs @@ -40,7 +40,7 @@ where } fn error_message(op_name: &str) -> String { - format!("There can only be one operation named {}", op_name) + format!("There can only be one operation named {op_name}") } #[cfg(test)] diff --git a/juniper/src/validation/rules/unique_variable_names.rs b/juniper/src/validation/rules/unique_variable_names.rs index 0c32706b..738e634b 100644 --- a/juniper/src/validation/rules/unique_variable_names.rs +++ b/juniper/src/validation/rules/unique_variable_names.rs @@ -46,7 +46,7 @@ where } fn error_message(var_name: &str) -> String { - format!("There can only be one variable named {}", var_name) + format!("There can only be one variable named {var_name}") } #[cfg(test)] diff --git a/juniper/src/validation/rules/variables_are_input_types.rs b/juniper/src/validation/rules/variables_are_input_types.rs index 0336b618..168119d6 100644 --- a/juniper/src/validation/rules/variables_are_input_types.rs +++ b/juniper/src/validation/rules/variables_are_input_types.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::{ ast::VariableDefinition, parser::Spanning, @@ -26,7 +28,7 @@ where { if !var_type.is_input() { ctx.report_error( - &error_message(var_name.item, &format!("{}", var_def.var_type.item)), + &error_message(var_name.item, &var_def.var_type.item), &[var_def.var_type.start], ); } @@ -34,11 +36,8 @@ where } } -fn error_message(var_name: &str, type_name: &str) -> String { - format!( - "Variable \"{}\" cannot be of non-input type \"{}\"", - var_name, type_name - ) +fn error_message(var_name: impl fmt::Display, type_name: impl fmt::Display) -> String { + format!("Variable \"{var_name}\" cannot be of non-input type \"{type_name}\"") } #[cfg(test)] diff --git a/juniper/src/validation/rules/variables_in_allowed_position.rs b/juniper/src/validation/rules/variables_in_allowed_position.rs index 1f3dff59..776f12db 100644 --- a/juniper/src/validation/rules/variables_in_allowed_position.rs +++ b/juniper/src/validation/rules/variables_in_allowed_position.rs @@ -1,7 +1,7 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - fmt::Debug, + fmt, }; use crate::{ @@ -17,15 +17,7 @@ pub enum Scope<'a> { Fragment(&'a str), } -pub struct VariableInAllowedPosition<'a, S: Debug + 'a> { - spreads: HashMap, HashSet<&'a str>>, - variable_usages: HashMap, Vec<(Spanning<&'a String>, Type<'a>)>>, - #[allow(clippy::type_complexity)] - variable_defs: HashMap, Vec<&'a (Spanning<&'a str>, VariableDefinition<'a, S>)>>, - current_scope: Option>, -} - -pub fn factory<'a, S: Debug>() -> VariableInAllowedPosition<'a, S> { +pub fn factory<'a, S: fmt::Debug>() -> VariableInAllowedPosition<'a, S> { VariableInAllowedPosition { spreads: HashMap::new(), variable_usages: HashMap::new(), @@ -34,16 +26,54 @@ pub fn factory<'a, S: Debug>() -> VariableInAllowedPosition<'a, S> { } } -impl<'a, S: Debug> VariableInAllowedPosition<'a, S> { - fn collect_incorrect_usages( - &self, +pub struct VariableInAllowedPosition<'a, S: fmt::Debug + 'a> { + spreads: HashMap, HashSet<&'a str>>, + variable_usages: HashMap, Vec<(Spanning<&'a String>, Type<'a>)>>, + #[allow(clippy::type_complexity)] + variable_defs: HashMap, Vec<&'a (Spanning<&'a str>, VariableDefinition<'a, S>)>>, + current_scope: Option>, +} + +impl<'a, S: fmt::Debug> VariableInAllowedPosition<'a, S> { + fn collect_incorrect_usages<'me>( + &'me self, from: &Scope<'a>, var_defs: &[&'a (Spanning<&'a str>, VariableDefinition)], ctx: &mut ValidatorContext<'a, S>, visited: &mut HashSet>, ) { + let mut to_visit = Vec::new(); + if let Some(spreads) = self.collect_incorrect_usages_inner(from, var_defs, ctx, visited) { + to_visit.push(spreads); + } + + while let Some(spreads) = to_visit.pop() { + for spread in spreads { + if let Some(spreads) = self.collect_incorrect_usages_inner( + &Scope::Fragment(spread), + var_defs, + ctx, + visited, + ) { + to_visit.push(spreads); + } + } + } + } + + /// This function should be called only inside + /// [`Self::collect_incorrect_usages()`], as it's a recursive function using + /// heap instead of a stack. So, instead of the recursive call, we return a + /// [`Vec`] that is visited inside [`Self::collect_incorrect_usages()`]. + fn collect_incorrect_usages_inner<'me>( + &'me self, + from: &Scope<'a>, + var_defs: &[&'a (Spanning<&'a str>, VariableDefinition)], + ctx: &mut ValidatorContext<'a, S>, + visited: &mut HashSet>, + ) -> Option<&'me HashSet<&'a str>> { if visited.contains(from) { - return; + return None; } visited.insert(from.clone()); @@ -66,11 +96,7 @@ impl<'a, S: Debug> VariableInAllowedPosition<'a, S> { if !ctx.schema.is_subtype(&expected_type, var_type) { ctx.report_error( - &error_message( - var_name.item, - &format!("{}", expected_type), - &format!("{}", var_type), - ), + &error_message(var_name.item, expected_type, var_type), &[var_def_name.start, var_name.start], ); } @@ -78,11 +104,7 @@ impl<'a, S: Debug> VariableInAllowedPosition<'a, S> { } } - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.collect_incorrect_usages(&Scope::Fragment(spread), var_defs, ctx, visited); - } - } + self.spreads.get(from) } } @@ -157,10 +179,13 @@ where } } -fn error_message(var_name: &str, type_name: &str, expected_type_name: &str) -> String { +fn error_message( + var_name: impl fmt::Display, + type_name: impl fmt::Display, + expected_type_name: impl fmt::Display, +) -> String { format!( - "Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"", - var_name, type_name, expected_type_name + "Variable \"{var_name}\" of type \"{type_name}\" used in position expecting type \"{expected_type_name}\"", ) } diff --git a/juniper/src/validation/test_harness.rs b/juniper/src/validation/test_harness.rs index 5bba0a73..c2f5ddac 100644 --- a/juniper/src/validation/test_harness.rs +++ b/juniper/src/validation/test_harness.rs @@ -20,6 +20,7 @@ use crate::{ struct Being; struct Pet; struct Canine; +struct Unpopulated; struct Dog; struct Cat; @@ -167,6 +168,41 @@ where } } +impl GraphQLType for Unpopulated +where + S: ScalarValue, +{ + fn name(_: &()) -> Option<&'static str> { + Some("Unpopulated") + } + + fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + let fields = &[registry + .field::>("name", i) + .argument(registry.arg::>("surname", i))]; + + registry + .build_interface_type::(i, fields) + .interfaces(&[registry.get_type::(i)]) + .into_meta() + } +} + +impl GraphQLValue for Unpopulated +where + S: ScalarValue, +{ + type Context = (); + type TypeInfo = (); + + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { + ::name(info) + } +} + impl GraphQLType for DogCommand where S: ScalarValue, @@ -688,6 +724,9 @@ where registry .field::>("stringListArgField", i) .argument(registry.arg::>>>("stringListArg", i)), + registry + .field::>("nonNullStringListArgField", i) + .argument(registry.arg::>("nonNullStringListArg", i)), registry .field::>("complexArgField", i) .argument(registry.arg::>("complexArg", i)), @@ -777,6 +816,8 @@ where where S: 'r, { + let _ = registry.get_type::(i); + let fields = [registry.field::("testInput", i).argument( registry.arg_with_default::( "input", @@ -882,7 +923,7 @@ where )); let doc = - parse_document_source(q, &root.schema).expect(&format!("Parse error on input {:#?}", q)); + parse_document_source(q, &root.schema).expect(&format!("Parse error on input {q:#?}")); let mut ctx = ValidatorContext::new(unsafe { mem::transmute(&root.schema) }, &doc); visit_fn(&mut ctx, unsafe { mem::transmute(doc.as_slice()) }); diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e3457c8e..6a160807 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -196,15 +196,15 @@ impl fmt::Display for Value { Self::Null => write!(f, "null"), Self::Scalar(s) => { if let Some(string) = s.as_string() { - write!(f, "\"{}\"", string) + write!(f, "\"{string}\"") } else { - write!(f, "{}", s) + write!(f, "{s}") } } Self::List(list) => { write!(f, "[")?; for (idx, item) in list.iter().enumerate() { - write!(f, "{}", item)?; + write!(f, "{item}")?; if idx < list.len() - 1 { write!(f, ", ")?; } @@ -216,7 +216,7 @@ impl fmt::Display for Value { Self::Object(obj) => { write!(f, "{{")?; for (idx, (key, value)) in obj.iter().enumerate() { - write!(f, "\"{}\": {}", key, value)?; + write!(f, "\"{key}\": {value}")?; if idx < obj.field_count() - 1 { write!(f, ", ")?; @@ -287,52 +287,52 @@ mod tests { #[test] fn display_null() { let s: Value = graphql_value!(null); - assert_eq!("null", format!("{}", s)); + assert_eq!(s.to_string(), "null"); } #[test] fn display_int() { let s: Value = graphql_value!(123); - assert_eq!("123", format!("{}", s)); + assert_eq!(s.to_string(), "123"); } #[test] fn display_float() { let s: Value = graphql_value!(123.456); - assert_eq!("123.456", format!("{}", s)); + assert_eq!(s.to_string(), "123.456"); } #[test] fn display_string() { let s: Value = graphql_value!("foo"); - assert_eq!("\"foo\"", format!("{}", s)); + assert_eq!(s.to_string(), "\"foo\""); } #[test] fn display_bool() { let s: Value = graphql_value!(false); - assert_eq!("false", format!("{}", s)); + assert_eq!(s.to_string(), "false"); let s: Value = graphql_value!(true); - assert_eq!("true", format!("{}", s)); + assert_eq!(s.to_string(), "true"); } #[test] fn display_list() { let s: Value = graphql_value!([1, null, "foo"]); - assert_eq!("[1, null, \"foo\"]", format!("{}", s)); + assert_eq!(s.to_string(), "[1, null, \"foo\"]"); } #[test] fn display_list_one_element() { let s: Value = graphql_value!([1]); - assert_eq!("[1]", format!("{}", s)); + assert_eq!(s.to_string(), "[1]"); } #[test] fn display_list_empty() { let s: Value = graphql_value!([]); - assert_eq!("[]", format!("{}", s)); + assert_eq!(s.to_string(), "[]"); } #[test] @@ -343,8 +343,8 @@ mod tests { "string": "foo", }); assert_eq!( + s.to_string(), r#"{"int": 1, "null": null, "string": "foo"}"#, - format!("{}", s) ); } @@ -353,12 +353,12 @@ mod tests { let s: Value = graphql_value!({ "int": 1, }); - assert_eq!(r#"{"int": 1}"#, format!("{}", s)); + assert_eq!(s.to_string(), r#"{"int": 1}"#); } #[test] fn display_object_empty() { let s: Value = graphql_value!({}); - assert_eq!(r#"{}"#, format!("{}", s)); + assert_eq!(s.to_string(), r#"{}"#); } } diff --git a/juniper/src/value/object.rs b/juniper/src/value/object.rs index 909abe3f..4e3deb41 100644 --- a/juniper/src/value/object.rs +++ b/juniper/src/value/object.rs @@ -1,4 +1,4 @@ -use std::{iter::FromIterator, mem}; +use std::mem; use super::Value; use indexmap::map::{IndexMap, IntoIter}; diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index d14e8a6a..f93e7bbf 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -7,12 +7,12 @@ use crate::parser::{ParseError, ScalarToken}; pub use juniper_codegen::ScalarValue; /// The result of converting a string into a scalar value -pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result>; +pub type ParseScalarResult = Result; /// A trait used to convert a `ScalarToken` into a certain scalar value type pub trait ParseScalarValue { /// See the trait documentation - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult; } /// A trait marking a type that could be used as internal representation of @@ -34,7 +34,7 @@ pub trait ParseScalarValue { /// integers. /// /// ```rust -/// # use std::{fmt, convert::TryInto as _}; +/// # use std::fmt; /// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; /// # use juniper::ScalarValue; @@ -228,20 +228,20 @@ pub trait ScalarValue: /// /// These types closely follow the [GraphQL specification][0]. /// -/// [0]: https://spec.graphql.org/June2018 +/// [0]: https://spec.graphql.org/October2021 #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value. /// - /// [0]: https://spec.graphql.org/June2018/#sec-Int + /// [0]: https://spec.graphql.org/October2021#sec-Int #[value(as_float, as_int)] Int(i32), /// [`Float` scalar][0] as a signed double‐precision fractional values as /// specified by [IEEE 754]. /// - /// [0]: https://spec.graphql.org/June2018/#sec-Float + /// [0]: https://spec.graphql.org/October2021#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point #[value(as_float)] Float(f64), @@ -249,13 +249,13 @@ pub enum DefaultScalarValue { /// [`String` scalar][0] as a textual data, represented as UTF‐8 character /// sequences. /// - /// [0]: https://spec.graphql.org/June2018/#sec-String + /// [0]: https://spec.graphql.org/October2021#sec-String #[value(as_str, as_string, into_string)] String(String), /// [`Boolean` scalar][0] as a `true` or `false` value. /// - /// [0]: https://spec.graphql.org/June2018/#sec-Boolean + /// [0]: https://spec.graphql.org/October2021#sec-Boolean #[value(as_bool)] Boolean(bool), } diff --git a/juniper_actix/Cargo.toml b/juniper_actix/Cargo.toml index f46a7498..0bcb8d4c 100644 --- a/juniper_actix/Cargo.toml +++ b/juniper_actix/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_actix" version = "0.5.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "`juniper` GraphQL integration with `actix-web`." license = "BSD-2-Clause" authors = ["Jordao Rosario "] @@ -26,7 +27,7 @@ actix-http = "3.0" actix-web = "4.0" actix-web-actors = "4.1.0" anyhow = "1.0" -futures = "0.3" +futures = "0.3.22" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } juniper_graphql_ws = { version = "0.4.0-dev", path = "../juniper_graphql_ws", optional = true } http = "0.2.4" @@ -37,9 +38,9 @@ tokio = { version = "1.0", features = ["sync"], optional = true } [dev-dependencies] actix-cors = "0.6" -actix-identity = "0.4" +actix-identity = "0.5" actix-rt = "2.0" -actix-test = "=0.1.0-beta.13" +actix-test = "0.1" async-stream = "0.3" bytes = "1.0" env_logger = "0.9" diff --git a/juniper_actix/examples/actix_server.rs b/juniper_actix/examples/actix_server.rs index 5a13b79a..ab27373d 100644 --- a/juniper_actix/examples/actix_server.rs +++ b/juniper_actix/examples/actix_server.rs @@ -21,7 +21,7 @@ pub struct User { name: String, } -#[derive(Default, Clone)] +#[derive(Clone, Default)] pub struct Database { ///this could be a database connection users: HashMap, @@ -33,28 +33,28 @@ impl Database { 1, User { id: 1, - name: "Aron".to_string(), + name: "Aron".into(), }, ); users.insert( 2, User { id: 2, - name: "Bea".to_string(), + name: "Bea".into(), }, ); users.insert( 3, User { id: 3, - name: "Carl".to_string(), + name: "Carl".into(), }, ); users.insert( 4, User { id: 4, - name: "Dora".to_string(), + name: "Dora".into(), }, ); Database { users } diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index c88c1b00..3c6192e6 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -368,7 +368,7 @@ pub mod subscriptions { Err(e) => { let reason = ws::CloseReason { code: ws::CloseCode::Error, - description: Some(format!("error serializing response: {}", e)), + description: Some(format!("error serializing response: {e}")), }; // TODO: trace @@ -389,7 +389,7 @@ pub mod subscriptions { #[derive(Debug)] struct Message(ws::Message); - impl std::convert::TryFrom for ClientMessage { + impl TryFrom for ClientMessage { type Error = Error; fn try_from(msg: Message) -> Result { @@ -416,7 +416,7 @@ pub mod subscriptions { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Serde(e) => write!(f, "serde error: {}", e), + Self::Serde(e) => write!(f, "serde error: {e}"), Self::UnexpectedClientMessage => { write!(f, "unexpected message received from client") } @@ -712,7 +712,7 @@ mod tests { self.make_request( TestRequest::post() .append_header(("content-type", "application/json")) - .set_payload(body.to_string()) + .set_payload(body.to_owned()) .uri(url), ) } @@ -721,7 +721,7 @@ mod tests { self.make_request( TestRequest::post() .append_header(("content-type", "application/graphql")) - .set_payload(body.to_string()) + .set_payload(body.to_owned()) .uri(url), ) } @@ -735,7 +735,7 @@ mod tests { .unwrap() .to_str() .unwrap() - .to_string(); + .into(); let body = take_response_body_string(resp).await; TestResponse { status_code: status_code as i32, @@ -797,29 +797,28 @@ mod subscription_tests { framed .send(ws::Message::Text(body.to_owned().into())) .await - .map_err(|e| anyhow::anyhow!("WS error: {:?}", e))?; + .map_err(|e| anyhow::anyhow!("WS error: {e:?}"))?; } WsIntegrationMessage::Expect(body, message_timeout) => { let frame = timeout(Duration::from_millis(*message_timeout), framed.next()) .await .map_err(|_| anyhow::anyhow!("Timed-out waiting for message"))? .ok_or_else(|| anyhow::anyhow!("Empty message received"))? - .map_err(|e| anyhow::anyhow!("WS error: {:?}", e))?; + .map_err(|e| anyhow::anyhow!("WS error: {e:?}"))?; match frame { ws::Frame::Text(ref bytes) => { let expected_value = serde_json::from_str::(body) - .map_err(|e| anyhow::anyhow!("Serde error: {:?}", e))?; + .map_err(|e| anyhow::anyhow!("Serde error: {e:?}"))?; let value: serde_json::Value = serde_json::from_slice(bytes) - .map_err(|e| anyhow::anyhow!("Serde error: {:?}", e))?; + .map_err(|e| anyhow::anyhow!("Serde error: {e:?}"))?; if value != expected_value { return Err(anyhow::anyhow!( - "Expected message: {}. Received message: {}", - expected_value, - value, + "Expected message: {expected_value}. \ + Received message: {value}", )); } } diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md index 2ce86c70..aa93555e 100644 --- a/juniper_codegen/CHANGELOG.md +++ b/juniper_codegen/CHANGELOG.md @@ -16,10 +16,11 @@ All user visible changes to `juniper_codegen` crate will be documented in this f - Removed support for `dyn` attribute argument (interface values as trait objects). - Removed support for `downcast` attribute argument (custom resolution into implementer types). - Removed support for `async` trait methods (not required anymore). - - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). + - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). ([#113]) - Forbade default implementations of non-ignored trait methods. - Supported coercion of additional `null`able arguments and return sub-typing on implementer. - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) + - Supported interfaces implementing other interfaces. ([#1028]) - Split `#[derive(GraphQLScalarValue)]` macro into: - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) - Supported generic `ScalarValue`. @@ -41,6 +42,7 @@ All user visible changes to `juniper_codegen` crate will be documented in this f - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) +[#113]: /../../issues/113 [#971]: /../../pull/971 [#985]: /../../pull/985 [#987]: /../../pull/987 @@ -51,6 +53,7 @@ All user visible changes to `juniper_codegen` crate will be documented in this f [#1017]: /../../pull/1017 [#1025]: /../../pull/1025 [#1026]: /../../pull/1026 +[#1028]: /../../pull/1028 [#1051]: /../../issues/1051 [#1054]: /../../pull/1054 diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 79ba4d63..b2291658 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_codegen" version = "0.16.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "Code generation for `juniper` crate." license = "BSD-2-Clause" authors = [ @@ -29,6 +30,6 @@ url = "2.0" [dev-dependencies] derive_more = "0.99.7" -futures = "0.3" +futures = "0.3.22" juniper = { path = "../juniper" } serde = "1.0" diff --git a/juniper_codegen/src/common/default.rs b/juniper_codegen/src/common/default.rs new file mode 100644 index 00000000..a2d00f45 --- /dev/null +++ b/juniper_codegen/src/common/default.rs @@ -0,0 +1,62 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of [GraphQL default values][0] +//! +//! [0]: https://spec.graphql.org/October2021#DefaultValue + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + token, +}; + +use crate::common::parse::ParseBufferExt as _; + +/// Representation of a [GraphQL default value][0] for code generation. +/// +/// [0]: https://spec.graphql.org/October2021#DefaultValue +#[derive(Clone, Debug, Default)] +pub(crate) enum Value { + /// [`Default`] implementation should be used. + #[default] + Default, + + /// Explicit [`Expr`]ession to be used as the [default value][0]. + /// + /// [`Expr`]: syn::Expr + /// [0]: https://spec.graphql.org/October2021#DefaultValue + Expr(Box), +} + +impl From> for Value { + fn from(opt: Option) -> Self { + match opt { + Some(expr) => Self::Expr(Box::new(expr)), + None => Self::Default, + } + } +} + +impl Parse for Value { + fn parse(input: ParseStream<'_>) -> syn::Result { + Ok(input + .try_parse::()? + .map(|_| input.parse::()) + .transpose()? + .into()) + } +} + +impl ToTokens for Value { + fn to_tokens(&self, into: &mut TokenStream) { + match self { + Self::Default => quote! { + ::std::default::Default::default() + }, + Self::Expr(expr) => quote! { + (#expr).into() + }, + } + .to_tokens(into) + } +} diff --git a/juniper_codegen/src/common/deprecation.rs b/juniper_codegen/src/common/deprecation.rs new file mode 100644 index 00000000..9efd6ef0 --- /dev/null +++ b/juniper_codegen/src/common/deprecation.rs @@ -0,0 +1,114 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of [GraphQL deprecation directive][0]. +//! +//! [0]: https://spec.graphql.org/October2021#sec--deprecated + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned as _, + token, +}; + +use crate::common::{parse::ParseBufferExt as _, SpanContainer}; + +/// [GraphQL deprecation directive][0] defined on a [GraphQL field][1] or a +/// [GraphQL enum value][2] via `#[graphql(deprecated = ...)]` (or +/// `#[deprecated(note = ...)]`) attribute. +/// +/// [0]: https://spec.graphql.org/October2021#sec--deprecated +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields +/// [2]: https://spec.graphql.org/October2021#sec-Enum-Value +#[derive(Debug, Default)] +pub(crate) struct Directive { + /// Optional [reason][1] attached to this [deprecation][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec--deprecated + /// [1]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L + pub(crate) reason: Option, +} + +impl Parse for Directive { + fn parse(input: ParseStream<'_>) -> syn::Result { + Ok(Self { + reason: input + .try_parse::()? + .map(|_| input.parse::()) + .transpose()?, + }) + } +} + +impl Directive { + /// Tries to parse a [`Directive`] from a `#[deprecated(note = ...)]` + /// attribute, by looking up for it in the provided [`syn::Attribute`]s. + /// + /// # Errors + /// + /// If failed to parse a [`Directive`] from a found + /// `#[deprecated(note = ...)]` attribute. + pub(crate) fn parse_from_deprecated_attr( + attrs: &[syn::Attribute], + ) -> syn::Result>> { + for attr in attrs { + return Ok(match attr.parse_meta() { + Ok(syn::Meta::List(ref list)) if list.path.is_ident("deprecated") => { + let directive = Self::parse_from_deprecated_meta_list(list)?; + Some(SpanContainer::new( + list.path.span(), + directive.reason.as_ref().map(|r| r.span()), + directive, + )) + } + Ok(syn::Meta::Path(ref path)) if path.is_ident("deprecated") => { + Some(SpanContainer::new(path.span(), None, Self::default())) + } + _ => continue, + }); + } + Ok(None) + } + + /// Tries to parse a [`Directive`] from the [`syn::MetaList`] of a single + /// `#[deprecated(note = ...)]` attribute. + /// + /// # Errors + /// + /// If the `#[deprecated(note = ...)]` attribute has incorrect format. + fn parse_from_deprecated_meta_list(list: &syn::MetaList) -> syn::Result { + for meta in &list.nested { + if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = meta { + return if !nv.path.is_ident("note") { + Err(syn::Error::new( + nv.path.span(), + "unrecognized setting on #[deprecated(..)] attribute", + )) + } else if let syn::Lit::Str(strlit) = &nv.lit { + Ok(Self { + reason: Some(strlit.clone()), + }) + } else { + Err(syn::Error::new( + nv.lit.span(), + "only strings are allowed for deprecation", + )) + }; + } + } + Ok(Self::default()) + } +} + +impl ToTokens for Directive { + fn to_tokens(&self, into: &mut TokenStream) { + let reason = self + .reason + .as_ref() + .map_or_else(|| quote! { None }, |text| quote! { Some(#text) }); + quote! { + .deprecated(::std::option::Option::#reason) + } + .to_tokens(into); + } +} diff --git a/juniper_codegen/src/common/description.rs b/juniper_codegen/src/common/description.rs new file mode 100644 index 00000000..cd8d739e --- /dev/null +++ b/juniper_codegen/src/common/description.rs @@ -0,0 +1,200 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of [GraphQL description][0]. +//! +//! [0]: https://spec.graphql.org/October2021#sec-Descriptions + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; + +use crate::common::SpanContainer; + +/// [GraphQL description][0] defined on a GraphQL definition via +/// `#[graphql(description = ...)]` (or `#[doc = ...]`) attribute. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Descriptions +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Description(syn::LitStr); + +impl Parse for Description { + fn parse(input: ParseStream<'_>) -> syn::Result { + input.parse::().map(Self) + } +} + +impl Description { + /// Tries to parse a [`Description`] from a `#[doc = ...]` attribute (or + /// Rust doc comment), by looking up for it in the provided + /// [`syn::Attribute`]s. + /// + /// # Errors + /// + /// If failed to parse a [`Description`] from a found `#[doc = ...]` + /// attribute. + pub(crate) fn parse_from_doc_attrs( + attrs: &[syn::Attribute], + ) -> syn::Result>> { + let (mut first_span, mut descriptions) = (None, Vec::new()); + for attr in attrs { + match attr.parse_meta() { + Ok(syn::Meta::NameValue(ref nv)) if nv.path.is_ident("doc") => { + if let syn::Lit::Str(strlit) = &nv.lit { + if first_span.is_none() { + first_span = Some(strlit.span()); + } + descriptions.push(strlit.value()); + } else { + return Err(syn::Error::new( + nv.lit.span(), + "#[doc] attributes may only have a string literal", + )); + } + } + _ => continue, + } + } + Ok(first_span.map(|span| { + SpanContainer::new( + span, + None, + Self(syn::LitStr::new(&Self::concatenate(&descriptions), span)), + ) + })) + } + + /// Concatenates [`Description`] strings into a single one. + fn concatenate(descriptions: &[String]) -> String { + let last_index = descriptions.len() - 1; + descriptions + .iter() + .map(|s| s.as_str().trim_end()) + .map(|s| { + // Trim leading space. + s.strip_prefix(' ').unwrap_or(s) + }) + .enumerate() + .fold(String::new(), |mut buffer, (index, s)| { + // Add newline, except when string ends in a continuation + // backslash or is the last line. + if index == last_index { + buffer.push_str(s); + } else if s.ends_with('\\') { + buffer.push_str(s.trim_end_matches('\\')); + buffer.push(' '); + } else { + buffer.push_str(s); + buffer.push('\n'); + } + buffer + }) + } +} + +impl ToTokens for Description { + fn to_tokens(&self, into: &mut TokenStream) { + let desc = &self.0; + + quote! { + .description(#desc) + } + .to_tokens(into); + } +} + +#[cfg(test)] +mod parse_from_doc_attrs_test { + use quote::quote; + use syn::parse_quote; + + use super::Description; + + #[test] + fn single() { + let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[doc = "foo"] }]) + .unwrap() + .unwrap() + .into_inner(); + assert_eq!( + quote! { #desc }.to_string(), + quote! { .description("foo") }.to_string(), + ); + } + + #[test] + fn many() { + let desc = Description::parse_from_doc_attrs(&[ + parse_quote! { #[doc = "foo"] }, + parse_quote! { #[doc = "\n"] }, + parse_quote! { #[doc = "bar"] }, + ]) + .unwrap() + .unwrap() + .into_inner(); + assert_eq!( + quote! { #desc }.to_string(), + quote! { .description("foo\n\nbar") }.to_string(), + ); + } + + #[test] + fn not_doc() { + let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[blah = "foo"] }]).unwrap(); + assert_eq!(desc, None); + } +} + +#[cfg(test)] +mod concatenate_test { + use super::Description; + + /// Forms a [`Vec`] of [`String`]s out of the provided [`str`]s + /// [`Iterator`]. + fn to_strings<'i>(source: impl IntoIterator) -> Vec { + source.into_iter().map(Into::into).collect() + } + + #[test] + fn single() { + assert_eq!(Description::concatenate(&to_strings(["foo"])), "foo"); + } + + #[test] + fn multiple() { + assert_eq!( + Description::concatenate(&to_strings(["foo", "bar"])), + "foo\nbar", + ); + } + + #[test] + fn trims_spaces() { + assert_eq!( + Description::concatenate(&to_strings([" foo ", "bar ", " baz"])), + "foo\nbar\nbaz", + ); + } + + #[test] + fn empty() { + assert_eq!( + Description::concatenate(&to_strings(["foo", "", "bar"])), + "foo\n\nbar", + ); + } + + #[test] + fn newline_spaces() { + assert_eq!( + Description::concatenate(&to_strings(["foo ", "", " bar"])), + "foo\n\nbar", + ); + } + + #[test] + fn continuation_backslash() { + assert_eq!( + Description::concatenate(&to_strings(["foo\\", "x\\", "y", "bar"])), + "foo x y\nbar", + ); + } +} diff --git a/juniper_codegen/src/common/diagnostic.rs b/juniper_codegen/src/common/diagnostic.rs new file mode 100644 index 00000000..d99bb504 --- /dev/null +++ b/juniper_codegen/src/common/diagnostic.rs @@ -0,0 +1,87 @@ +use std::fmt; + +use proc_macro2::Span; +use proc_macro_error::{Diagnostic, Level}; + +/// URL of the GraphQL specification (October 2021 Edition). +pub(crate) const SPEC_URL: &str = "https://spec.graphql.org/October2021"; + +pub(crate) enum Scope { + EnumDerive, + InputObjectDerive, + InterfaceAttr, + InterfaceDerive, + ObjectAttr, + ObjectDerive, + ScalarAttr, + ScalarDerive, + ScalarValueDerive, + UnionAttr, + UnionDerive, +} + +impl Scope { + pub(crate) fn spec_section(&self) -> &str { + match self { + Self::EnumDerive => "#sec-Enums", + Self::InputObjectDerive => "#sec-Input-Objects", + Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", + Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", + Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", + Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", + Self::UnionAttr | Self::UnionDerive => "#sec-Unions", + } + } +} + +impl fmt::Display for Scope { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = match self { + Self::EnumDerive => "enum", + Self::InputObjectDerive => "input object", + Self::InterfaceAttr | Self::InterfaceDerive => "interface", + Self::ObjectAttr | Self::ObjectDerive => "object", + Self::ScalarAttr | Self::ScalarDerive => "scalar", + Self::ScalarValueDerive => "built-in scalars", + Self::UnionAttr | Self::UnionDerive => "union", + }; + write!(f, "GraphQL {name}") + } +} + +impl Scope { + fn spec_link(&self) -> String { + format!("{SPEC_URL}{}", self.spec_section()) + } + + pub(crate) fn custom>(&self, span: Span, msg: S) -> Diagnostic { + Diagnostic::spanned(span, Level::Error, format!("{self} {}", msg.as_ref())) + .note(self.spec_link()) + } + + pub(crate) fn error(&self, err: syn::Error) -> Diagnostic { + Diagnostic::spanned(err.span(), Level::Error, format!("{self} {err}")) + .note(self.spec_link()) + } + + pub(crate) fn emit_custom>(&self, span: Span, msg: S) { + self.custom(span, msg).emit() + } + + pub(crate) fn custom_error>(&self, span: Span, msg: S) -> syn::Error { + syn::Error::new(span, format!("{self} {}", msg.as_ref())) + } + + pub(crate) 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." + .into(), + ) + .note(format!("{SPEC_URL}#sec-Schema")) + .emit(); + } +} diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 9eeb72cf..afbe53ed 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -1,7 +1,7 @@ //! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL arguments][1] //! -//! [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments. +//! [1]: https://spec.graphql.org/October2021#sec-Language.Arguments. use std::mem; @@ -14,22 +14,19 @@ use syn::{ token, }; -use crate::{ - common::{ - parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, TypeExt as _, - }, - scalar, +use crate::common::{ + default, diagnostic, filter_attrs, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, TypeExt as _, }, - result::GraphQLScope, - util::{filter_attrs, path_eq_single, span_container::SpanContainer, RenameRule}, + path_eq_single, rename, scalar, Description, SpanContainer, }; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a /// method argument, when generating code for [GraphQL argument][1]. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments #[derive(Debug, Default)] pub(crate) struct Attr { /// Explicitly specified name of a [GraphQL argument][1] represented by this @@ -37,26 +34,23 @@ pub(crate) struct Attr { /// /// If [`None`], then `camelCased` Rust argument name is used by default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL argument][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + pub(crate) description: Option>, /// Explicitly specified [default value][2] of this [GraphQL argument][1]. /// - /// If the exact default expression is not specified, then the [`Default`] - /// value is used. - /// /// If [`None`], then this [GraphQL argument][1] is considered as /// [required][2]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments - pub(crate) default: Option>>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments + pub(crate) default: Option>, /// Explicitly specified marker indicating that this method argument doesn't /// represent a [GraphQL argument][1], but is a [`Context`] being injected @@ -66,8 +60,8 @@ pub(crate) struct Attr { /// if it's named `context` or `ctx`. /// /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) context: Option>, /// Explicitly specified marker indicating that this method argument doesn't @@ -78,8 +72,8 @@ pub(crate) struct Attr { /// if it's named `executor`. /// /// [`Executor`]: juniper::Executor - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) executor: Option>, } @@ -98,27 +92,15 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "default" => { - let mut expr = None; - if input.is_next::() { - input.parse::()?; - expr = Some(input.parse::()?); - } else if input.is_next::() { - let inner; - let _ = syn::parenthesized!(inner in input); - expr = Some(inner.parse::()?); - } + let val = input.parse::()?; out.default - .replace(SpanContainer::new( - ident.span(), - expr.as_ref().map(|e| e.span()), - expr, - )) + .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { @@ -213,46 +195,40 @@ impl Attr { fn err_disallowed(span: &S, arg: &str) -> syn::Error { syn::Error::new( span.span(), - format!( - "attribute argument `#[graphql({} = ...)]` is not allowed here", - arg, - ), + format!("attribute argument `#[graphql({arg} = ...)]` is not allowed here",), ) } } /// Representation of a [GraphQL field argument][1] for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments #[derive(Debug)] pub(crate) struct OnField { /// Rust type that this [GraphQL field argument][1] is represented by. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) ty: syn::Type, /// Name of this [GraphQL field argument][2] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) name: String, /// [Description][2] of this [GraphQL field argument][1] to put into GraphQL /// schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + pub(crate) description: Option, /// Default value of this [GraphQL field argument][1] in GraphQL schema. /// - /// If outer [`Option`] is [`None`], then this [argument][1] is a - /// [required][2] one. + /// If [`None`], then this [argument][1] is a [required][2] one. /// - /// If inner [`Option`] is [`None`], then the [`Default`] value is used. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments - pub(crate) default: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments + pub(crate) default: Option, } /// Possible kinds of Rust method arguments for code generation. @@ -260,19 +236,19 @@ pub(crate) struct OnField { pub(crate) enum OnMethod { /// Regular [GraphQL field argument][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments Regular(Box), /// [`Context`] passed into a [GraphQL field][2] resolving method. /// /// [`Context`]: juniper::Context - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields Context(Box), /// [`Executor`] passed into a [GraphQL field][2] resolving method. /// /// [`Executor`]: juniper::Executor - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields Executor, } @@ -281,7 +257,7 @@ impl OnMethod { #[must_use] pub(crate) fn as_regular(&self) -> Option<&OnField> { if let Self::Regular(arg) = self { - Some(&*arg) + Some(&**arg) } else { None } @@ -292,7 +268,7 @@ impl OnMethod { #[must_use] pub(crate) fn context_ty(&self) -> Option<&syn::Type> { if let Self::Context(ty) = self { - Some(&*ty) + Some(&**ty) } else { None } @@ -323,16 +299,9 @@ impl OnMethod { let (name, ty) = (&arg.name, &arg.ty); - let description = arg - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); + let description = &arg.description; let method = if let Some(val) = &arg.default { - let val = val - .as_ref() - .map(|v| quote! { (#v).into() }) - .unwrap_or_else(|| quote! { <#ty as Default>::default() }); quote_spanned! { val.span() => .arg_with_default::<#ty>(#name, &#val, info) } @@ -340,7 +309,7 @@ impl OnMethod { quote! { .arg::<#ty>(#name, info) } }; - Some(quote! { .argument(registry#method#description) }) + Some(quote! { .argument(registry #method #description) }) } /// Returns generated code for the [`GraphQLValue::resolve_field`] method, @@ -357,7 +326,7 @@ impl OnMethod { match self { Self::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); - let err_text = format!("Missing argument `{}`: {{}}", &name); + let err_text = format!("Missing argument `{name}`: {{}}"); let arg = quote! { args.get::<#ty>(#name).and_then(|opt| opt.map_or_else(|| { @@ -395,8 +364,8 @@ impl OnMethod { /// given `scope`. pub(crate) fn parse( argument: &mut syn::PatType, - renaming: &RenameRule, - scope: &GraphQLScope, + renaming: &rename::Policy, + scope: &diagnostic::Scope, ) -> Option { let orig_attrs = argument.attrs.clone(); @@ -462,8 +431,8 @@ impl OnMethod { Some(Self::Regular(Box::new(OnField { name, ty: argument.ty.as_ref().clone(), - description: attr.description.as_ref().map(|d| d.as_ref().value()), - default: attr.default.as_ref().map(|v| v.as_ref().clone()), + description: attr.description.map(SpanContainer::into_inner), + default: attr.default.map(SpanContainer::into_inner), }))) } } diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 2efe969f..8e8cd297 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -1,7 +1,7 @@ //! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL fields][1] //! -//! [1]: https://spec.graphql.org/June2018/#sec-Language.Fields. +//! [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) mod arg; @@ -14,15 +14,13 @@ use syn::{ token, }; -use crate::{ - common::{ - parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, - }, - scalar, +use crate::common::{ + deprecation, filter_attrs, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, }, - util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, + scalar, Description, SpanContainer, }; pub(crate) use self::arg::OnMethod as MethodArgument; @@ -30,39 +28,39 @@ pub(crate) use self::arg::OnMethod as MethodArgument; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a /// [GraphQL field][1] definition. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[derive(Debug, Default)] pub(crate) struct Attr { /// Explicitly specified name of this [GraphQL field][1]. /// /// If [`None`], then `camelCased` Rust method name is used by default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL field][1]. /// - /// If [`None`], then Rust doc comment is used as the [description][2], if - /// any. + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + pub(crate) description: Option>, /// Explicitly specified [deprecation][2] of this [GraphQL field][1]. /// - /// If [`None`], then Rust `#[deprecated]` attribute is used as the + /// If [`None`], then Rust `#[deprecated]` attribute will be used as the /// [deprecation][2], if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation - pub(crate) deprecated: Option>>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + pub(crate) deprecated: Option>, /// Explicitly specified marker indicating that this method (or struct /// field) should be omitted by code generation and not considered as the /// [GraphQL field][1] definition. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ignore: Option>, } @@ -81,22 +79,18 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "deprecated" => { - let mut reason = None; - if input.is_next::() { - input.parse::()?; - reason = Some(input.parse::()?); - } + let directive = input.parse::()?; out.deprecated .replace(SpanContainer::new( ident.span(), - reason.as_ref().map(|r| r.span()), - reason, + directive.reason.as_ref().map(|r| r.span()), + directive, )) .none_or_else(|_| err::dup_arg(&ident))? } @@ -129,7 +123,7 @@ impl Attr { /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a [GraphQL field][1] definition. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) @@ -145,17 +139,11 @@ impl Attr { } if attr.description.is_none() { - attr.description = get_doc_comment(attrs).map(|sc| { - let span = sc.span_ident(); - sc.map(|desc| syn::LitStr::new(&desc, span)) - }); + attr.description = Description::parse_from_doc_attrs(attrs)?; } if attr.deprecated.is_none() { - attr.deprecated = get_deprecated(attrs).map(|sc| { - let span = sc.span_ident(); - sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span))) - }); + attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; } Ok(attr) @@ -164,39 +152,36 @@ impl Attr { /// Representation of a [GraphQL field][1] for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[derive(Debug)] pub(crate) struct Definition { /// Rust type that this [GraphQL field][1] is represented by (method return /// type or struct field type). /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ty: syn::Type, /// Name of this [GraphQL field][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) name: String, /// [Description][2] of this [GraphQL field][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + pub(crate) description: Option, /// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema. /// - /// If inner [`Option`] is [`None`], then deprecation has no message - /// attached. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation - pub(crate) deprecated: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + pub(crate) deprecated: Option, /// Ident of the Rust method (or struct field) representing this /// [GraphQL field][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ident: syn::Ident, /// Rust [`MethodArgument`]s required to call the method representing this @@ -205,19 +190,19 @@ pub(crate) struct Definition { /// If [`None`] then this [GraphQL field][1] is represented by a struct /// field. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) arguments: Option>, /// Indicator whether the Rust method representing this [GraphQL field][1] /// has a [`syn::Receiver`]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) has_receiver: bool, /// Indicator whether this [GraphQL field][1] should be resolved /// asynchronously. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) is_async: bool, } @@ -225,7 +210,7 @@ impl Definition { /// Indicates whether this [GraphQL field][1] is represented by a method, /// not a struct field. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn is_method(&self) -> bool { self.arguments.is_some() @@ -235,14 +220,14 @@ impl Definition { /// tried to be resolved in the [`GraphQLValue::resolve_field`] method. /// /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_resolve_field_err_no_field_tokens( scalar: &scalar::Type, ty_name: &str, ) -> TokenStream { quote! { - return Err(::juniper::FieldError::from(format!( + return Err(::juniper::FieldError::from(::std::format!( "Field `{}` not found on type `{}`", field, >::name(info) @@ -255,7 +240,7 @@ impl Definition { /// which performs static checks for this [GraphQL field][1]. /// /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_mark_tokens( &self, @@ -291,7 +276,7 @@ impl Definition { /// /// [`GraphQLType::meta`]: juniper::GraphQLType::meta /// [`Registry`]: juniper::Registry - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_meta_tokens( &self, @@ -305,18 +290,8 @@ impl Definition { }; } - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let deprecated = self.deprecated.as_ref().map(|reason| { - let reason = reason - .as_ref() - .map(|rsn| quote! { Some(#rsn) }) - .unwrap_or_else(|| quote! { None }); - quote! { .deprecated(#reason) } - }); + let description = &self.description; + let deprecated = &self.deprecated; let args = self .arguments @@ -336,8 +311,8 @@ impl Definition { /// resolves this [GraphQL field][1] as [subscription][2]. /// /// [0]: juniper::GraphQLSubscriptionValue::resolve_field_into_stream - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Subscription + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] pub(crate) fn method_resolve_field_into_stream_tokens( &self, @@ -375,7 +350,7 @@ impl Definition { let stream = ::juniper::futures::StreamExt::then(res, move |res| { let executor = executor.clone(); let res2: ::juniper::FieldResult<_, #scalar> = - ::juniper::IntoResolvable::into(res, executor.context()); + ::juniper::IntoResolvable::into_resolvable(res, executor.context()); async move { let ex = executor.as_executor(); match res2 { @@ -401,7 +376,7 @@ impl Definition { /// Checks whether all [GraphQL fields][1] fields have different names. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn all_different(fields: &[Definition]) -> bool { let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect(); diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs index dc5adc13..a0b5e731 100644 --- a/juniper_codegen/src/common/gen.rs +++ b/juniper_codegen/src/common/gen.rs @@ -8,10 +8,10 @@ use quote::quote; /// Value of a [GraphQL type][1] should be stored in a `res` binding in the generated code, before /// including this piece of code. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Types +/// [1]: https://spec.graphql.org/October2021#sec-Types pub(crate) fn sync_resolving_code() -> TokenStream { quote! { - ::juniper::IntoResolvable::into(res, executor.context()) + ::juniper::IntoResolvable::into_resolvable(res, executor.context()) .and_then(|res| match res { Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), None => Ok(::juniper::Value::null()), @@ -29,13 +29,13 @@ pub(crate) fn sync_resolving_code() -> TokenStream { /// /// [`Future`]: std::future::Future /// [`Future::Output`]: std::future::Future::Output -/// [1]: https://spec.graphql.org/June2018/#sec-Types +/// [1]: https://spec.graphql.org/October2021#sec-Types pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream { let ty = ty.map(|t| quote! { : #t }); quote! { Box::pin(::juniper::futures::FutureExt::then(fut, move |res #ty| async move { - match ::juniper::IntoResolvable::into(res, executor.context())? { + match ::juniper::IntoResolvable::into_resolvable(res, executor.context())? { Some((ctx, r)) => { let subexec = executor.replaced_context(ctx); subexec.resolve_with_ctx_async(info, &r).await diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index fd16d954..e6e3dd8f 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,6 +1,31 @@ //! Common functions, definitions and extensions for code generation, used by this crate. +pub(crate) mod default; +pub(crate) mod deprecation; +mod description; +pub(crate) mod diagnostic; pub(crate) mod field; pub(crate) mod gen; pub(crate) mod parse; +pub(crate) mod rename; pub(crate) mod scalar; +mod span_container; + +pub(crate) use self::{description::Description, span_container::SpanContainer}; + +/// Checks whether the specified [`syn::Path`] equals to one-segment string +/// `value`. +pub(crate) fn path_eq_single(path: &syn::Path, value: &str) -> bool { + path.segments.len() == 1 && path.segments[0].ident == value +} + +/// Filters the provided [`syn::Attribute`] to contain only ones with the +/// specified `name`. +pub(crate) fn filter_attrs<'a>( + name: &'a str, + attrs: &'a [syn::Attribute], +) -> impl Iterator + 'a { + attrs + .iter() + .filter(move |attr| path_eq_single(&attr.path, name)) +} diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs index 9e42cc97..8b3eceec 100644 --- a/juniper_codegen/src/common/parse/attr.rs +++ b/juniper_codegen/src/common/parse/attr.rs @@ -4,7 +4,7 @@ use proc_macro2::{Span, TokenStream}; use syn::parse_quote; -use crate::util::path_eq_single; +use crate::common::path_eq_single; /// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given /// `attr_path` and `attr_args`. @@ -49,7 +49,7 @@ pub(crate) mod err { pub(crate) fn unknown_arg(span: S, name: &str) -> syn::Error { syn::Error::new( span.as_span(), - format!("unknown `{}` attribute argument", name), + format!("unknown `{name}` attribute argument"), ) } diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs index 89dd67cd..8c63de21 100644 --- a/juniper_codegen/src/common/parse/downcaster.rs +++ b/juniper_codegen/src/common/parse/downcaster.rs @@ -1,8 +1,8 @@ //! Common functions, definitions and extensions for parsing downcasting functions, used by GraphQL //! [interfaces][1] and [unions][2] definitions to downcast its type to a concrete implementer type. //! -//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces -//! [2]: https://spec.graphql.org/June2018/#sec-Unions +//! [1]: https://spec.graphql.org/October2021#sec-Interfaces +//! [2]: https://spec.graphql.org/October2021#sec-Unions use proc_macro2::Span; use syn::{ext::IdentExt as _, spanned::Spanned as _}; @@ -17,7 +17,7 @@ use crate::common::parse::TypeExt as _; /// corresponding error at. pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result { let ret_ty = match &ret_ty { - syn::ReturnType::Type(_, ty) => &*ty, + syn::ReturnType::Type(_, ty) => &**ty, _ => return Err(ret_ty.span()), }; diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index eaa946d9..53781675 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -4,11 +4,7 @@ pub(crate) mod attr; pub(crate) mod downcaster; -use std::{ - any::TypeId, - iter::{self, FromIterator as _}, - mem, -}; +use std::{any::TypeId, iter, mem}; use proc_macro2::Span; use quote::quote; diff --git a/juniper_codegen/src/common/rename.rs b/juniper_codegen/src/common/rename.rs new file mode 100644 index 00000000..d7c0cac9 --- /dev/null +++ b/juniper_codegen/src/common/rename.rs @@ -0,0 +1,164 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of `#[graphql(rename_all = ...)]` attribute. + +use std::str::FromStr; + +use syn::parse::{Parse, ParseStream}; + +/// Possible ways to rename all [GraphQL fields][1] or [GrqphQL enum values][2]. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields +/// [2]: https://spec.graphql.org/October2021#sec-Enum-Value +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum Policy { + /// Do nothing, and use the default conventions renaming. + None, + + /// Rename in `camelCase` style. + CamelCase, + + /// Rename in `SCREAMING_SNAKE_CASE` style. + ScreamingSnakeCase, +} + +impl Policy { + /// Applies this [`Policy`] to the given `name`. + pub(crate) fn apply(&self, name: &str) -> String { + match self { + Self::None => name.into(), + Self::CamelCase => to_camel_case(name), + Self::ScreamingSnakeCase => to_upper_snake_case(name), + } + } +} + +impl FromStr for Policy { + type Err = (); + + fn from_str(rule: &str) -> Result { + match rule { + "none" => Ok(Self::None), + "camelCase" => Ok(Self::CamelCase), + "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase), + _ => Err(()), + } + } +} + +impl TryFrom for Policy { + type Error = syn::Error; + + fn try_from(lit: syn::LitStr) -> syn::Result { + Self::from_str(&lit.value()) + .map_err(|_| syn::Error::new(lit.span(), "unknown renaming policy")) + } +} + +impl Parse for Policy { + fn parse(input: ParseStream<'_>) -> syn::Result { + Self::try_from(input.parse::()?) + } +} + +// NOTE: duplicated from juniper crate! +fn to_camel_case(s: &str) -> String { + let mut dest = String::new(); + + // Handle `_` and `__` to be more friendly with the `_var` convention for + // unused variables, and GraphQL introspection identifiers. + let s_iter = if let Some(s) = s.strip_prefix("__") { + dest.push_str("__"); + s + } else { + s.strip_prefix('_').unwrap_or(s) + } + .split('_') + .enumerate(); + + for (i, part) in s_iter { + if i > 0 && part.len() == 1 { + dest.push_str(&part.to_uppercase()); + } else if i > 0 && part.len() > 1 { + let first = part + .chars() + .next() + .unwrap() + .to_uppercase() + .collect::(); + let second = &part[1..]; + + dest.push_str(&first); + dest.push_str(second); + } else if i == 0 { + dest.push_str(part); + } + } + + dest +} + +fn to_upper_snake_case(s: &str) -> String { + let mut last_lower = false; + let mut upper = String::new(); + for c in s.chars() { + if c == '_' { + last_lower = false; + } else if c.is_lowercase() { + last_lower = true; + } else if c.is_uppercase() { + if last_lower { + upper.push('_'); + } + last_lower = false; + } + + for u in c.to_uppercase() { + upper.push(u); + } + } + upper +} + +#[cfg(test)] +mod to_camel_case_tests { + use super::to_camel_case; + + #[test] + fn converts_correctly() { + for (input, expected) in [ + ("test", "test"), + ("_test", "test"), + ("__test", "__test"), + ("first_second", "firstSecond"), + ("first_", "first"), + ("a_b_c", "aBC"), + ("a_bc", "aBc"), + ("a_b", "aB"), + ("a", "a"), + ("", ""), + ] { + assert_eq!(to_camel_case(input), expected); + } + } +} + +#[cfg(test)] +mod to_upper_snake_case_tests { + use super::to_upper_snake_case; + + #[test] + fn converts_correctly() { + for (input, expected) in [ + ("abc", "ABC"), + ("a_bc", "A_BC"), + ("ABC", "ABC"), + ("A_BC", "A_BC"), + ("SomeInput", "SOME_INPUT"), + ("someInput", "SOME_INPUT"), + ("someINpuT", "SOME_INPU_T"), + ("some_INpuT", "SOME_INPU_T"), + ] { + assert_eq!(to_upper_snake_case(input), expected); + } + } +} diff --git a/juniper_codegen/src/util/span_container.rs b/juniper_codegen/src/common/span_container.rs similarity index 75% rename from juniper_codegen/src/util/span_container.rs rename to juniper_codegen/src/common/span_container.rs index 370f17a7..bb27a03b 100644 --- a/juniper_codegen/src/util/span_container.rs +++ b/juniper_codegen/src/common/span_container.rs @@ -6,8 +6,8 @@ use std::{ use proc_macro2::{Span, TokenStream}; use quote::ToTokens; -#[derive(Clone, Debug)] -pub struct SpanContainer { +#[derive(Clone, Copy, Debug)] +pub(crate) struct SpanContainer { expr: Option, ident: Span, val: T, @@ -20,15 +20,15 @@ impl ToTokens for SpanContainer { } impl SpanContainer { - pub fn new(ident: Span, expr: Option, val: T) -> Self { + pub(crate) fn new(ident: Span, expr: Option, val: T) -> Self { Self { expr, ident, val } } - pub fn span_ident(&self) -> Span { + pub(crate) fn span_ident(&self) -> Span { self.ident } - pub fn span_joined(&self) -> Span { + pub(crate) fn span_joined(&self) -> Span { if let Some(s) = self.expr { // TODO: Use `Span::join` once stabilized and available on stable: // https://github.com/rust-lang/rust/issues/54725 @@ -41,21 +41,9 @@ impl SpanContainer { } } - pub fn into_inner(self) -> T { + pub(crate) fn into_inner(self) -> T { self.val } - - pub fn inner(&self) -> &T { - &self.val - } - - pub fn map U>(self, f: F) -> SpanContainer { - SpanContainer { - expr: self.expr, - ident: self.ident, - val: f(self.val), - } - } } impl AsRef for SpanContainer { diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs deleted file mode 100644 index 458bc43e..00000000 --- a/juniper_codegen/src/derive_enum.rs +++ /dev/null @@ -1,153 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ext::IdentExt, spanned::Spanned, Data, Fields}; - -use crate::{ - result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer, RenameRule}, -}; - -pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ast_span = ast.span(); - - if !ast.generics.params.is_empty() { - return Err(error.custom_error(ast_span, "does not support generics or lifetimes")); - } - - let variants = match ast.data { - Data::Enum(enum_data) => enum_data.variants, - _ => return Err(error.custom_error(ast_span, "can only be applied to enums")), - }; - - // Parse attributes. - let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?; - let ident = &ast.ident; - let name = attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| ident.unraw().to_string()); - - let fields = variants - .into_iter() - .filter_map(|field| { - let span = field.span(); - let field_attrs = match util::FieldAttributes::from_attrs( - &field.attrs, - util::FieldAttributeParseMode::Object, - ) { - Ok(attrs) => attrs, - Err(err) => { - proc_macro_error::emit_error!(err); - return None; - } - }; - - let field_name = field.ident; - let name = field_attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| { - attrs - .rename - .unwrap_or(RenameRule::ScreamingSnakeCase) - .apply(&field_name.unraw().to_string()) - }); - - let resolver_code = quote!( #ident::#field_name ); - - let _type = match field.fields { - Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(), - _ => { - error.emit_custom( - field.fields.span(), - "all fields of the enum must be unnamed, e.g., None", - ); - return None; - } - }; - - if let Some(skip) = field_attrs.skip { - error.unsupported_attribute(skip.span(), UnsupportedAttribute::Skip); - return None; - } - - 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::>(); - - proc_macro_error::abort_if_dirty(); - - if fields.is_empty() { - error.not_empty(ast_span); - } - if let Some(duplicates) = - crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) - { - error.duplicate(duplicates.iter()) - } - - 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 !attrs.is_internal && name.starts_with("__") { - 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 { - name, - _type: syn::parse_str(&ast.ident.to_string()).unwrap(), - context: attrs.context.map(SpanContainer::into_inner), - scalar: None, - description: attrs.description.map(SpanContainer::into_inner), - fields, - // NOTICE: only unit variants allow -> no generics possible - generics: syn::Generics::default(), - interfaces: vec![], - include_type_generics: true, - generic_scalar: true, - no_async: attrs.no_async.is_some(), - }; - - Ok(definition.into_enum_tokens()) -} diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs deleted file mode 100644 index 48d63dbc..00000000 --- a/juniper_codegen/src/derive_input_object.rs +++ /dev/null @@ -1,151 +0,0 @@ -#![allow(clippy::match_wild_err_arm)] -use crate::{ - result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer, RenameRule}, -}; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields}; - -pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ast_span = ast.span(); - let fields = match ast.data { - Data::Struct(data) => match data.fields { - Fields::Named(named) => named.named, - _ => { - return Err( - 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")), - }; - - // Parse attributes. - let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?; - - // Parse attributes. - let ident = &ast.ident; - let name = attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| ident.to_string()); - - let fields = fields - .into_iter() - .filter_map(|field| { - let span = field.span(); - let field_attrs = match util::FieldAttributes::from_attrs( - &field.attrs, - util::FieldAttributeParseMode::Object, - ) { - Ok(attrs) => attrs, - Err(e) => { - proc_macro_error::emit_error!(e); - return None; - } - }; - - let field_ident = field.ident.as_ref().unwrap(); - let name = match field_attrs.name { - Some(ref name) => name.to_string(), - None => attrs - .rename - .unwrap_or(RenameRule::CamelCase) - .apply(&field_ident.unraw().to_string()), - }; - - if let Some(span) = field_attrs.skip { - error.unsupported_attribute_within(span.span(), UnsupportedAttribute::Skip) - } - - if let Some(span) = field_attrs.deprecation { - error.unsupported_attribute_within( - span.span_ident(), - UnsupportedAttribute::Deprecation, - ) - } - - if name.starts_with("__") { - error.no_double_underscore(if let Some(name) = field_attrs.name { - name.span_ident() - } else { - name.span() - }); - } - - let resolver_code = quote!(#field_ident); - - let default = field_attrs - .default - .map(|default| match default.into_inner() { - Some(expr) => expr.into_token_stream(), - None => quote! { Default::default() }, - }); - - Some(util::GraphQLTypeDefinitionField { - name, - _type: field.ty, - args: Vec::new(), - description: field_attrs.description.map(SpanContainer::into_inner), - deprecation: None, - resolver_code, - is_type_inferred: true, - is_async: false, - default, - span, - }) - }) - .collect::>(); - - proc_macro_error::abort_if_dirty(); - - if fields.is_empty() { - error.not_empty(ast_span); - } - - if let Some(duplicates) = - crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) - { - error.duplicate(duplicates.iter()) - } - - 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 !attrs.is_internal && name.starts_with("__") { - 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 { - name, - _type: syn::parse_str(&ast.ident.to_string()).unwrap(), - context: attrs.context.map(SpanContainer::into_inner), - scalar: attrs.scalar.map(SpanContainer::into_inner), - description: attrs.description.map(SpanContainer::into_inner), - fields, - generics: ast.generics, - interfaces: vec![], - include_type_generics: true, - generic_scalar: true, - no_async: attrs.no_async.is_some(), - }; - - Ok(definition.into_input_object_tokens()) -} diff --git a/juniper_codegen/src/graphql_enum/derive.rs b/juniper_codegen/src/graphql_enum/derive.rs new file mode 100644 index 00000000..f0564a26 --- /dev/null +++ b/juniper_codegen/src/graphql_enum/derive.rs @@ -0,0 +1,134 @@ +//! Code generation for `#[derive(GraphQLEnum)]` macro. + +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::ToTokens as _; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::common::{diagnostic, rename, scalar, SpanContainer}; + +use super::{ContainerAttr, Definition, ValueDefinition, VariantAttr}; + +/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLEnum)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::EnumDerive; + +/// Expands `#[derive(GraphQLEnum)]` macro into generated code. +pub(crate) fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?; + + let data = if let syn::Data::Enum(data) = &ast.data { + data + } else { + return Err(ERR.custom_error(ast.span(), "can only be derived on enums")); + }; + + let mut has_ignored_variants = false; + let renaming = attr + .rename_values + .map(SpanContainer::into_inner) + .unwrap_or(rename::Policy::ScreamingSnakeCase); + let values = data + .variants + .iter() + .filter_map(|v| { + parse_value(v, renaming).or_else(|| { + has_ignored_variants = true; + None + }) + }) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if values.is_empty() { + return Err(ERR.custom_error( + data.variants.span(), + "expected at least 1 non-ignored enum variant", + )); + } + + let unique_values = values.iter().map(|v| &v.name).collect::>(); + if unique_values.len() != values.len() { + return Err(ERR.custom_error( + data.variants.span(), + "expected all GraphQL enum values to have unique names", + )); + } + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| ast.ident.unraw().to_string()) + .into_boxed_str(); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| ast.ident.span()), + ); + } + + let context = attr + .context + .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner); + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let definition = Definition { + ident: ast.ident, + generics: ast.generics, + name, + description: attr.description.map(SpanContainer::into_inner), + context, + scalar, + values, + has_ignored_variants, + }; + + Ok(definition.into_token_stream()) +} + +/// Parses a [`ValueDefinition`] from the given Rust enum variant definition. +/// +/// Returns [`None`] if the parsing fails, or the enum variant is ignored. +fn parse_value(v: &syn::Variant, renaming: rename::Policy) -> Option { + let attr = VariantAttr::from_attrs("graphql", &v.attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + if !v.fields.is_empty() { + err_variant_with_fields(&v.fields)?; + } + + let name = attr + .name + .map_or_else( + || renaming.apply(&v.ident.unraw().to_string()), + SpanContainer::into_inner, + ) + .into_boxed_str(); + + Some(ValueDefinition { + ident: v.ident.clone(), + name, + description: attr.description.map(SpanContainer::into_inner), + deprecated: attr.deprecated.map(SpanContainer::into_inner), + }) +} + +/// Emits "no fields allowed for non-ignored variants" [`syn::Error`] pointing +/// to the given `span`. +pub fn err_variant_with_fields(span: &S) -> Option { + ERR.emit_custom(span.span(), "no fields allowed for non-ignored variants"); + None +} diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs new file mode 100644 index 00000000..c14356f6 --- /dev/null +++ b/juniper_codegen/src/graphql_enum/mod.rs @@ -0,0 +1,745 @@ +//! Code generation for [GraphQL enums][0]. +//! +//! [0]: https://spec.graphql.org/October2021#sec-Enums + +pub(crate) mod derive; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::common::{ + deprecation, filter_attrs, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + rename, scalar, Description, SpanContainer, +}; + +/// Available arguments behind `#[graphql]` attribute placed on a Rust enum +/// definition, when generating code for a [GraphQL enum][0]. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Enums +#[derive(Debug, Default)] +struct ContainerAttr { + /// Explicitly specified name of this [GraphQL enum][0]. + /// + /// If [`None`], then Rust enum name will be used by default. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL enum][0]. + /// + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, + + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL enum][0] type with. + /// + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. + /// + /// [`Context`]: juniper::Context + /// [0]: https://spec.graphql.org/October2021#sec-Enums + context: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to resolve this [GraphQL enum][0] type with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + scalar: Option>, + + /// Explicitly specified [`rename::Policy`] for all [values][1] of this + /// [GraphQL enum][0]. + /// + /// If [`None`], then the [`rename::Policy::ScreamingSnakeCase`] will be + /// applied by default. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + /// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition + rename_values: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + is_internal: bool, +} + +impl Parse for ContainerAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + out.context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + out.rename_values + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + out.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl ContainerAttr { + /// Tries to merge two [`ContainerAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + context: try_merge_opt!(context: self, another), + scalar: try_merge_opt!(scalar: self, another), + rename_values: try_merge_opt!(rename_values: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`ContainerAttr`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a trait definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = Description::parse_from_doc_attrs(attrs)?; + } + + Ok(attr) + } +} + +/// Available arguments behind `#[graphql]` attribute when generating code for +/// a [GraphQL enum][0]'s [value][1]. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Enums +/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value +#[derive(Debug, Default)] +struct VariantAttr { + /// Explicitly specified name of this [GraphQL enum value][1]. + /// + /// If [`None`], then Rust enum variant's name will be used by default. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL enum value][1]. + /// + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, + + /// Explicitly specified [deprecation][2] of this [GraphQL enum value][1]. + /// + /// If [`None`], then Rust `#[deprecated]` attribute will be used as the + /// [deprecation][2], if any. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + /// [2]: https://spec.graphql.org/October2021#sec--deprecated + /// [3]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L + deprecated: Option>, + + /// Explicitly specified marker for the Rust enum variant to be ignored and + /// not included into the code generated for a [GraphQL enum][0] + /// implementation. + /// + /// [0]: https://spec.graphql.org/October20210#sec-Enums + ignore: Option>, +} + +impl Parse for VariantAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "deprecated" => { + let directive = input.parse::()?; + out.deprecated + .replace(SpanContainer::new( + ident.span(), + directive.reason.as_ref().map(|r| r.span()), + directive, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ignore" | "skip" => out + .ignore + .replace(SpanContainer::new(ident.span(), None, ident.clone())) + .none_or_else(|_| err::dup_arg(&ident))?, + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl VariantAttr { + /// Tries to merge two [`VariantAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + deprecated: try_merge_opt!(deprecated: self, another), + ignore: try_merge_opt!(ignore: self, another), + }) + } + + /// Parses [`VariantAttr`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a trait definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = Description::parse_from_doc_attrs(attrs)?; + } + + if attr.deprecated.is_none() { + attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; + } + + Ok(attr) + } +} + +/// Representation of a [GraphQL enum value][1] for code generation. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value +#[derive(Debug)] +struct ValueDefinition { + /// [`Ident`] of the Rust enum variant behind this [GraphQL enum value][1]. + /// + /// [`Ident`]: syn::Ident + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + ident: syn::Ident, + + /// Name of this [GraphQL enum value][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + name: Box, + + /// [Description][2] of this [GraphQL enum value][1] to put into GraphQL + /// schema. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option, + + /// [Deprecation][2] of this [GraphQL enum value][1] to put into GraphQL + /// schema. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value + /// [2]: https://spec.graphql.org/October2021#sec--deprecated + deprecated: Option, +} + +/// Representation of a [GraphQL enum][0] for code generation. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Enums +struct Definition { + /// [`Ident`] of the Rust enum behind this [GraphQL enum][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + ident: syn::Ident, + + /// [`Generics`] of the Rust enum behind this [GraphQL enum][0]. + /// + /// [`Generics`]: syn::Generics + /// [0]: https://spec.graphql.org/October2021#sec-Enums + generics: syn::Generics, + + /// Name of this [GraphQL enum][0] in GraphQL schema. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + name: Box, + + /// [Description][2] of this [GraphQL enum][0] to put into GraphQL schema. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option, + + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL enum][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [0]: https://spec.graphql.org/October2021#sec-Enums + context: syn::Type, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL enum][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + scalar: scalar::Type, + + /// [Values][1] of this [GraphQL enum][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + /// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition + values: Vec, + + /// Indicates whether the Rust enum behind this [GraphQL enum][0] contains + /// ignored variants. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Enums + has_ignored_variants: bool, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_input_and_output_type_tokens().to_tokens(into); + self.impl_graphql_type_tokens().to_tokens(into); + self.impl_graphql_value_tokens().to_tokens(into); + self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_from_input_value_tokens().to_tokens(into); + self.impl_to_input_value_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL enum][0]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_input_and_output_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::marker::IsInputType<#scalar> + for #ident #ty_generics + #where_clause {} + + #[automatically_derived] + impl #impl_generics ::juniper::marker::IsOutputType<#scalar> + for #ident #ty_generics + #where_clause {} + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL enum][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_graphql_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let name = &self.name; + let description = &self.description; + + let variants_meta = self.values.iter().map(|v| { + let v_name = &v.name; + let v_description = &v.description; + let v_deprecation = &v.deprecated; + + quote! { + ::juniper::meta::EnumValue::new(#v_name) + #v_description + #v_deprecation + } + }); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::GraphQLType<#scalar> + for #ident #ty_generics + #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + let variants = [#( #variants_meta ),*]; + + registry.build_enum_type::<#ident #ty_generics>(info, &variants) + #description + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL enum][0]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_graphql_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let context = &self.context; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.values.iter().map(|v| { + let ident = &v.ident; + let name = &v.name; + + quote! { + Self::#ident => Ok(::juniper::Value::scalar(String::from(#name))), + } + }); + + let ignored = self.has_ignored_variants.then(|| { + quote! { + _ => Err(::juniper::FieldError::<#scalar>::from( + "Cannot resolve ignored enum variant", + )), + } + }); + + quote! { + impl #impl_generics ::juniper::GraphQLValue<#scalar> + for #ident #ty_generics + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + + fn resolve( + &self, + _: &(), + _: Option<&[::juniper::Selection<#scalar>]>, + _: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match self { + #( #variants )* + #ignored + } + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL enum][0]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> + for #ident #ty_generics + #where_clause + { + fn resolve_async<'__a>( + &'__a self, + info: &'__a Self::TypeInfo, + selection_set: Option<&'__a [::juniper::Selection<#scalar>]>, + executor: &'__a ::juniper::Executor, + ) -> ::juniper::BoxFuture<'__a, ::juniper::ExecutionResult<#scalar>> { + let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); + Box::pin(::juniper::futures::future::ready(v)) + } + } + } + } + + /// Returns generated code implementing [`FromInputValue`] trait for this + /// [GraphQL enum][0]. + /// + /// [`FromInputValue`]: juniper::FromInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_from_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.values.iter().map(|v| { + let ident = &v.ident; + let name = &v.name; + + quote! { + Some(#name) => Ok(Self::#ident), + } + }); + + quote! { + impl #impl_generics ::juniper::FromInputValue<#scalar> + for #ident #ty_generics + #where_clause + { + type Error = ::std::string::String; + + fn from_input_value(v: &::juniper::InputValue<#scalar>) -> Result { + match v.as_enum_value().or_else(|| v.as_string_value()) { + #( #variants )* + _ => Err(::std::format!("Unknown enum value: {}", v)), + } + } + } + } + } + + /// Returns generated code implementing [`ToInputValue`] trait for this + /// [GraphQL enum][0]. + /// + /// [`ToInputValue`]: juniper::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_to_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.values.iter().map(|v| { + let var_ident = &v.ident; + let name = &v.name; + + quote! { + #ident::#var_ident => ::juniper::InputValue::<#scalar>::scalar( + String::from(#name), + ), + } + }); + + let ignored = self.has_ignored_variants.then(|| { + quote! { + _ => panic!("Cannot resolve ignored enum variant"), + } + }); + + quote! { + impl #impl_generics ::juniper::ToInputValue<#scalar> + for #ident #ty_generics + #where_clause + { + fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { + match self { + #( #variants )* + #ignored + } + } + } + } + } + + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL enum][0]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`WrappedType`]: juniper::macros::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Enums + fn impl_reflection_traits_tokens(&self) -> TokenStream { + let ident = &self.ident; + let name = &self.name; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ident #ty_generics + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ident #ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; + } + + impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ident #ty_generics + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this enum. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{ident}"); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ident = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ident #ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs new file mode 100644 index 00000000..370fc200 --- /dev/null +++ b/juniper_codegen/src/graphql_input_object/derive.rs @@ -0,0 +1,129 @@ +//! Code generation for `#[derive(GraphQLInputObject)]` macro. + +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::ToTokens as _; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::common::{diagnostic, rename, scalar, SpanContainer}; + +use super::{ContainerAttr, Definition, FieldAttr, FieldDefinition}; + +/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLInputObject)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::InputObjectDerive; + +/// Expands `#[derive(GraphQLInputObject)]` macro into generated code. +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?; + + let data = if let syn::Data::Struct(data) = &ast.data { + data + } else { + return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); + }; + + let renaming = attr + .rename_fields + .map(SpanContainer::into_inner) + .unwrap_or(rename::Policy::CamelCase); + + let is_internal = attr.is_internal; + let fields = data + .fields + .iter() + .filter_map(|f| parse_field(f, renaming, is_internal)) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if !fields.iter().any(|f| !f.ignored) { + return Err(ERR.custom_error(data.fields.span(), "expected at least 1 non-ignored field")); + } + + let unique_fields = fields.iter().map(|v| &v.name).collect::>(); + if unique_fields.len() != fields.len() { + return Err(ERR.custom_error( + data.fields.span(), + "expected all fields to have unique names", + )); + } + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| ast.ident.unraw().to_string()) + .into_boxed_str(); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| ast.ident.span()), + ); + } + + let context = attr + .context + .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner); + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let definition = Definition { + ident: ast.ident, + generics: ast.generics, + name, + description: attr.description.map(SpanContainer::into_inner), + context, + scalar, + fields, + }; + + Ok(definition.into_token_stream()) +} + +/// Parses a [`FieldDefinition`] from the given struct field definition. +/// +/// Returns [`None`] if the parsing fails. +fn parse_field( + f: &syn::Field, + renaming: rename::Policy, + is_internal: bool, +) -> Option { + let field_attr = FieldAttr::from_attrs("graphql", &f.attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?; + + let name = field_attr + .name + .map_or_else( + || renaming.apply(&ident.unraw().to_string()), + SpanContainer::into_inner, + ) + .into_boxed_str(); + if !is_internal && name.starts_with("__") { + ERR.no_double_underscore(f.span()); + } + + Some(FieldDefinition { + ident: ident.clone(), + ty: f.ty.clone(), + default: field_attr.default.map(SpanContainer::into_inner), + name, + description: field_attr.description.map(SpanContainer::into_inner), + ignored: field_attr.ignore.is_some(), + }) +} + +/// Emits "expected named struct field" [`syn::Error`] pointing to the given +/// `span`. +pub(crate) fn err_unnamed_field(span: &S) -> Option { + ERR.emit_custom(span.span(), "expected named struct field"); + None +} diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs new file mode 100644 index 00000000..93c21d6f --- /dev/null +++ b/juniper_codegen/src/graphql_input_object/mod.rs @@ -0,0 +1,778 @@ +//! Code generation for [GraphQL input objects][0]. +//! +//! [0]: https://spec.graphql.org/October2021#sec-Input-Objects + +pub(crate) mod derive; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, +}; + +use crate::common::{ + default, filter_attrs, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + rename, scalar, Description, SpanContainer, +}; + +/// Available arguments behind `#[graphql]` attribute placed on a Rust struct +/// definition, when generating code for a [GraphQL input object][0]. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects +#[derive(Debug, Default)] +struct ContainerAttr { + /// Explicitly specified name of this [GraphQL input object][0]. + /// + /// If [`None`], then Rust struct name will be used by default. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL input object][0]. + /// + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, + + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL input object][0] type with. + /// + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. + /// + /// [`Context`]: juniper::Context + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + context: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to use for resolving this [GraphQL input object][0] type + /// with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + scalar: Option>, + + /// Explicitly specified [`rename::Policy`] for all fields of this + /// [GraphQL input object][0]. + /// + /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by + /// default. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + rename_fields: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + is_internal: bool, +} + +impl Parse for ContainerAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + out.context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + out.rename_fields + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + out.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl ContainerAttr { + /// Tries to merge two [`ContainerAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + context: try_merge_opt!(context: self, another), + scalar: try_merge_opt!(scalar: self, another), + rename_fields: try_merge_opt!(rename_fields: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`ContainerAttr`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a struct or impl block definition. + pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = Description::parse_from_doc_attrs(attrs)?; + } + + Ok(attr) + } +} + +/// Available arguments behind `#[graphql]` attribute when generating code for +/// [GraphQL input object][0]'s [field][1]. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects +/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition +#[derive(Debug, Default)] +struct FieldAttr { + /// Explicitly specified name of this [GraphQL input object field][1]. + /// + /// If [`None`], then Rust struct field name will be used by default. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + name: Option>, + + /// Explicitly specified [default value][2] of this + /// [GraphQL input object field][1] to be used used in case a field value is + /// not provided. + /// + /// If [`None`], the this [field][1] will have no [default value][2]. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#DefaultValue + default: Option>, + + /// Explicitly specified [description][2] of this + /// [GraphQL input object field][1]. + /// + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, + + /// Explicitly specified marker for the Rust struct field to be ignored and + /// not included into the code generated for a [GraphQL input object][0] + /// implementation. + /// + /// Ignored Rust struct fields still consider the [`default`] attribute's + /// argument. + /// + /// [`default`]: Self::default + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + ignore: Option>, +} + +impl Parse for FieldAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "default" => { + let val = input.parse::()?; + out.default + .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ignore" | "skip" => out + .ignore + .replace(SpanContainer::new(ident.span(), None, ident.clone())) + .none_or_else(|_| err::dup_arg(&ident))?, + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl FieldAttr { + /// Tries to merge two [`FieldAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + default: try_merge_opt!(default: self, another), + description: try_merge_opt!(description: self, another), + ignore: try_merge_opt!(ignore: self, another), + }) + } + + /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a trait definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = Description::parse_from_doc_attrs(attrs)?; + } + + Ok(attr) + } +} + +/// Representation of a [GraphQL input object field][1] for code generation. +/// +/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition +#[derive(Debug)] +struct FieldDefinition { + /// [`Ident`] of the Rust struct field behind this + /// [GraphQL input object field][1]. + /// + /// [`Ident`]: syn::Ident + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + ident: syn::Ident, + + /// Rust type that this [GraphQL input object field][1] is represented with. + /// + /// It should contain all its generics, if any. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + ty: syn::Type, + + /// [Default value][2] of this [GraphQL input object field][1] to be used in + /// case a [field][1] value is not provided. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#DefaultValue + default: Option, + + /// Name of this [GraphQL input object field][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + name: Box, + + /// [Description][2] of this [GraphQL input object field][1] to put into + /// GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option, + + /// Indicator whether the Rust struct field behinds this + /// [GraphQL input object field][1] is being ignored and should not be + /// included into the generated code. + /// + /// Ignored Rust struct fields still consider the [`default`] attribute's + /// argument. + /// + /// [`default`]: Self::default + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + ignored: bool, +} + +/// Representation of [GraphQL input object][0] for code generation. +/// +/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects +#[derive(Debug)] +struct Definition { + /// [`Ident`] of the Rust struct behind this [GraphQL input object][0]. + /// + /// [`Ident`]: syn::Ident + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + ident: syn::Ident, + + /// [`Generics`] of the Rust enum behind this [GraphQL input object][0]. + /// + /// [`Generics`]: syn::Generics + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + generics: syn::Generics, + + /// Name of this [GraphQL input object][0] in GraphQL schema. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + name: Box, + + /// [Description][2] of this [GraphQL input object][0] to put into GraphQL + /// schema. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option, + + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL input object][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + context: syn::Type, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL input object][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + scalar: scalar::Type, + + /// [Fields][1] of this [GraphQL input object][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition + fields: Vec, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_input_type_tokens().to_tokens(into); + self.impl_graphql_type_tokens().to_tokens(into); + self.impl_graphql_value_tokens().to_tokens(into); + self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_from_input_value_tokens().to_tokens(into); + self.impl_to_input_value_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`marker::IsInputType`] trait for + /// this [GraphQL input object][0]. + /// + /// [`marker::IsInputType`]: juniper::marker::IsInputType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_input_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let assert_fields_input_values = self.fields.iter().filter_map(|f| { + let ty = &f.ty; + + (!f.ignored).then(|| { + quote! { + <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::marker::IsInputType<#scalar> + for #ident #ty_generics + #where_clause + { + fn mark() { + #( #assert_fields_input_values )* + } + } + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL input object][0]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_graphql_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let name = &self.name; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let description = &self.description; + + let fields = self.fields.iter().filter_map(|f| { + let ty = &f.ty; + let name = &f.name; + + (!f.ignored).then(|| { + let arg = if let Some(default) = &f.default { + quote! { .arg_with_default::<#ty>(#name, &#default, info) } + } else { + quote! { .arg::<#ty>(#name, info) } + }; + let description = &f.description; + + quote! { registry #arg #description } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::GraphQLType<#scalar> + for #ident #ty_generics + #where_clause + { + fn name(_: &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar>, + ) -> ::juniper::meta::MetaType<'r, #scalar> + where + #scalar: 'r, + { + let fields = [#( #fields ),*]; + registry + .build_input_object_type::<#ident #ty_generics>(info, &fields) + #description + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL input object][0]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let context = &self.context; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::GraphQLValue<#scalar> + for #ident #ty_generics + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL input object][0]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + #[allow(non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> + for #ident #ty_generics + #where_clause {} + } + } + + /// Returns generated code implementing [`FromInputValue`] trait for this + /// [GraphQL input object][0]. + /// + /// [`FromInputValue`]: juniper::FromInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_from_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let fields = self.fields.iter().map(|f| { + let ident = &f.ident; + + let construct = if f.ignored { + f.default.as_ref().map_or_else( + || { + let expr = default::Value::default(); + quote! { #expr } + }, + |expr| quote! { #expr }, + ) + } else { + let name = &f.name; + + let fallback = f.default.as_ref().map_or_else( + || { + quote! { + ::juniper::FromInputValue::<#scalar>::from_implicit_null() + .map_err(::juniper::IntoFieldError::into_field_error)? + } + }, + |expr| quote! { #expr }, + ); + + quote! { + match obj.get(#name) { + Some(v) => { + ::juniper::FromInputValue::<#scalar>::from_input_value(v) + .map_err(::juniper::IntoFieldError::into_field_error)? + } + None => { #fallback } + } + } + }; + + quote! { #ident: { #construct }, } + }); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::FromInputValue<#scalar> + for #ident #ty_generics + #where_clause + { + type Error = ::juniper::FieldError<#scalar>; + + fn from_input_value( + value: &::juniper::InputValue<#scalar>, + ) -> Result { + let obj = value + .to_object_value() + .ok_or_else(|| ::juniper::FieldError::<#scalar>::from( + ::std::format!("Expected input object, found: {}", value)) + )?; + + Ok(#ident { + #( #fields )* + }) + } + } + } + } + + /// Returns generated code implementing [`ToInputValue`] trait for this + /// [GraphQL input object][0]. + /// + /// [`ToInputValue`]: juniper::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_to_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let fields = self.fields.iter().filter_map(|f| { + let ident = &f.ident; + let name = &f.name; + + (!f.ignored).then(|| { + quote! { + (#name, ::juniper::ToInputValue::to_input_value(&self.#ident)) + } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::ToInputValue<#scalar> + for #ident #ty_generics + #where_clause + { + fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { + ::juniper::InputValue::object( + #[allow(deprecated)] + ::std::array::IntoIter::new([#( #fields ),*]) + .collect() + ) + } + } + } + } + + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL input object][0]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`WrappedType`]: juniper::macros::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + #[must_use] + fn impl_reflection_traits_tokens(&self) -> TokenStream { + let ident = &self.ident; + let name = &self.name; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ident #ty_generics + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ident #ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; + } + + impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ident #ty_generics + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this struct. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{ident}"); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ident = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ident #ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 746b7af7..634a1419 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -6,20 +6,16 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; -use crate::{ - common::{ - field, - parse::{self, TypeExt as _}, - scalar, - }, - result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer, RenameRule}, +use crate::common::{ + diagnostic, field, + parse::{self, TypeExt as _}, + path_eq_single, rename, scalar, SpanContainer, }; use super::{enum_idents, Attr, Definition}; -/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. -const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; +/// [`diagnostic::Scope`] of errors for `#[graphql_interface]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { @@ -55,7 +51,8 @@ fn expand_on_trait( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| trait_ident.unraw().to_string()); + .unwrap_or_else(|| trait_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -73,7 +70,7 @@ fn expand_on_trait( .rename_fields .as_deref() .copied() - .unwrap_or(RenameRule::CamelCase); + .unwrap_or(rename::Policy::CamelCase); let fields = ast .items @@ -120,17 +117,22 @@ fn expand_on_trait( enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), suppress_dead_code: None, - src_intra_doc_link: format!("trait@{}", trait_ident), + src_intra_doc_link: format!("trait@{trait_ident}").into_boxed_str(), }; Ok(quote! { @@ -145,7 +147,7 @@ fn expand_on_trait( #[must_use] fn parse_trait_method( method: &mut syn::TraitItemMethod, - renaming: &RenameRule, + renaming: &rename::Policy, ) -> Option { let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); @@ -199,17 +201,11 @@ fn parse_trait_method( }; ty.lifetimes_anonymized(); - let description = attr.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = attr - .deprecated - .as_deref() - .map(|d| d.as_ref().map(syn::LitStr::value)); - Some(field::Definition { name, ty, - description, - deprecated, + description: attr.description.map(SpanContainer::into_inner), + deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), @@ -242,7 +238,8 @@ fn expand_on_derive_input( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| struct_ident.unraw().to_string()); + .unwrap_or_else(|| struct_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -260,7 +257,7 @@ fn expand_on_derive_input( .rename_fields .as_deref() .copied() - .unwrap_or(RenameRule::CamelCase); + .unwrap_or(rename::Policy::CamelCase); let fields = data .fields @@ -301,17 +298,22 @@ fn expand_on_derive_input( enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), suppress_dead_code: None, - src_intra_doc_link: format!("struct@{}", struct_ident), + src_intra_doc_link: format!("struct@{struct_ident}").into_boxed_str(), }; Ok(quote! { @@ -325,7 +327,10 @@ fn expand_on_derive_input( /// /// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] -fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option { +fn parse_struct_field( + field: &mut syn::Field, + renaming: &rename::Policy, +) -> Option { let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let field_attrs = field.attrs.clone(); @@ -361,17 +366,11 @@ fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option syn::Result { @@ -33,7 +29,8 @@ pub fn expand(input: TokenStream) -> syn::Result { .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| struct_ident.unraw().to_string()); + .unwrap_or_else(|| struct_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -51,7 +48,7 @@ pub fn expand(input: TokenStream) -> syn::Result { .rename_fields .as_deref() .copied() - .unwrap_or(RenameRule::CamelCase); + .unwrap_or(rename::Policy::CamelCase); let fields = data .fields @@ -93,17 +90,22 @@ pub fn expand(input: TokenStream) -> syn::Result { enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), - src_intra_doc_link: format!("struct@{}", struct_ident), + src_intra_doc_link: format!("struct@{struct_ident}").into_boxed_str(), } .into_token_stream()) } @@ -112,7 +114,7 @@ pub fn expand(input: TokenStream) -> syn::Result { /// /// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] -fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { +fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option { let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let attr = field::Attr::from_attrs("graphql", &field.attrs) @@ -141,17 +143,11 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option, ) -> (syn::Ident, syn::Ident) { let enum_alias_ident = alias_ident .cloned() - .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + .unwrap_or_else(|| format_ident!("{trait_ident}Value")); let enum_ident = alias_ident.map_or_else( - || format_ident!("{}ValueEnum", trait_ident.to_string()), - |c| format_ident!("{}Enum", c.to_string()), + || format_ident!("{trait_ident}ValueEnum"), + |c| format_ident!("{c}Enum"), ); (enum_ident, enum_alias_ident) } @@ -54,23 +51,24 @@ fn enum_idents( /// trait or struct definition, when generating code for [GraphQL interface][1] /// type. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[derive(Debug, Default)] struct Attr { /// Explicitly specified name of [GraphQL interface][1] type. /// /// If [`None`], then Rust trait name is used by default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces name: Option>, /// Explicitly specified [description][2] of [GraphQL interface][1] type. /// - /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - description: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, /// Explicitly specified identifier of the type alias of Rust enum type /// behind the trait or struct, being an actual implementation of a @@ -78,23 +76,30 @@ struct Attr { /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces r#enum: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing - /// this [GraphQL interface][1] type. + /// Explicitly specified Rust types of [GraphQL objects][2] or + /// [interfaces][1] implementing this [GraphQL interface][1] type. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Objects implemented_for: HashSet>, + /// Explicitly specified [GraphQL interfaces, implemented][1] by this + /// [GraphQL interface][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b + implements: HashSet>, + /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces context: Option>, /// Explicitly specified type (or type parameter with its bounds) of @@ -108,7 +113,7 @@ struct Attr { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces scalar: Option>, /// Explicitly specified marker indicating that the Rust trait should be @@ -118,13 +123,14 @@ struct Attr { /// it contains async methods. asyncness: Option>, - /// Explicitly specified [`RenameRule`] for all fields of this + /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL interface][1] type. /// - /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. + /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by + /// default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - rename_fields: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. @@ -150,13 +156,9 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { @@ -185,6 +187,18 @@ impl Parse for Attr { .none_or_else(|_| err::dup_arg(impler_span))?; } } + "impl" | "implements" => { + input.parse::()?; + for iface in input.parse_maybe_wrapped_and_punctuated::< + syn::TypePath, token::Bracket, token::Comma, + >()? { + let iface_span = iface.span(); + out + .implements + .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) + .none_or_else(|_| err::dup_arg(iface_span))?; + } + } "enum" => { input.parse::()?; let alias = input.parse::()?; @@ -232,6 +246,7 @@ impl Attr { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), + implements: try_merge_hashset!(implements: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), @@ -247,7 +262,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { - attr.description = get_doc_comment(attrs); + attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) @@ -256,18 +271,18 @@ impl Attr { /// Definition of [GraphQL interface][1] for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces struct Definition { /// [`syn::Generics`] of the trait or struct describing the /// [GraphQL interface][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces generics: syn::Generics, /// [`syn::Visibility`] of the trait or struct describing the /// [GraphQL interface][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces vis: syn::Visibility, /// Name of the generic enum describing all [`implementers`]. It's generic @@ -283,22 +298,22 @@ struct Definition { /// [`implementers`]: Self::implementers enum_alias_ident: syn::Ident, - /// Name of this [GraphQL interface][1] in GraphQL schema. + /// Name of this [GraphQL interface][0] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - name: String, + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + name: Box, - /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// Description of this [GraphQL interface][0] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - description: Option, + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] @@ -306,33 +321,39 @@ struct Definition { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields fields: Vec, /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces implemented_for: Vec, + /// [GraphQL interfaces implemented][1] by this [GraphQL interface][0]. + /// + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b + implements: Vec, + /// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't /// append `#[allow(dead_code)]` to the unused struct, representing /// [GraphQL interface][1]. We generate hacky `const` which doesn't actually /// use it, but suppresses this warning. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces suppress_dead_code: Option<(syn::Ident, syn::Fields)>, /// Intra-doc link to the [`syn::Item`] defining this - /// [GraphQL interface][1]. + /// [GraphQL interface][0]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - src_intra_doc_link: String, + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + src_intra_doc_link: Box, } impl ToTokens for Definition { @@ -361,7 +382,7 @@ impl Definition { let alias_ident = &self.enum_alias_ident; let variant_gens_pars = (0..self.implemented_for.len()).map::(|id| { - let par = format_ident!("__I{}", id); + let par = format_ident!("__I{id}"); parse_quote! { #par } }); let variants_idents = self @@ -413,13 +434,13 @@ impl Definition { "Enum building an opaque value represented by [`{}`]({}) \ [GraphQL interface][0].\ \n\n\ - [0]: https://spec.graphql.org/June2018/#sec-Interfaces", + [0]: https://spec.graphql.org/October2021#sec-Interfaces", self.name, self.src_intra_doc_link, ); let enum_alias_doc = format!( "Opaque value represented by [`{}`]({}) [GraphQL interface][0].\ \n\n\ - [0]: https://spec.graphql.org/June2018/#sec-Interfaces", + [0]: https://spec.graphql.org/October2021#sec-Interfaces", self.name, self.src_intra_doc_link, ); @@ -453,8 +474,8 @@ impl Definition { .map(|(ty, ident)| { quote! { #[automatically_derived] - impl#interface_impl_gens ::std::convert::From<#ty> - for #alias_ident#interface_ty_gens + impl #interface_impl_gens ::std::convert::From<#ty> + for #alias_ident #interface_ty_gens #interface_where_clause { fn from(v: #ty) -> Self { @@ -468,14 +489,14 @@ impl Definition { #[automatically_derived] #[derive(Clone, Copy, Debug)] #[doc = #enum_doc] - #vis enum #enum_ident#enum_gens { + #vis enum #enum_ident #enum_gens { #( #[doc(hidden)] #variants_idents(#variant_gens_pars), )* #( #[doc(hidden)] #phantom_variant, )* } #[automatically_derived] #[doc = #enum_alias_doc] - #vis type #alias_ident#enum_alias_gens = + #vis type #alias_ident #enum_alias_gens = #enum_ident<#( #enum_to_alias_gens ),*>; #( #from_impls )* @@ -486,7 +507,7 @@ impl Definition { /// [GraphQL interface][1]. /// /// [`GraphQLInterface`]: juniper::GraphQLInterface - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -496,18 +517,13 @@ impl Definition { let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); - let implemented_for = &self.implemented_for; - let all_impled_for_unique = (implemented_for.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } - }); - let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| { let const_gens = self.const_trait_generics(); let fields = fields.iter().map(|f| &f.ident); quote! {{ const SUPPRESS_DEAD_CODE: () = { - let none = Option::<#ident#const_gens>::None; + let none = Option::<#ident #const_gens>::None; match none { Some(unreachable) => { #( let _ = unreachable.#fields; )* @@ -519,16 +535,59 @@ impl Definition { }} }); + let implemented_for = &self.implemented_for; + let all_impled_for_unique = (implemented_for.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } + }); + + let mark_object_or_interface = self.implemented_for.iter().map(|impl_for| { + quote_spanned! { impl_for.span() => + trait GraphQLObjectOrInterface { + fn mark(); + } + + { + struct Object; + + impl GraphQLObjectOrInterface for T + where + S: ::juniper::ScalarValue, + T: ::juniper::marker::GraphQLObject, + { + fn mark() { + >::mark() + } + } + } + + { + struct Interface; + + impl GraphQLObjectOrInterface for T + where + S: ::juniper::ScalarValue, + T: ::juniper::marker::GraphQLInterface, + { + fn mark() { + >::mark() + } + } + } + + <#impl_for as GraphQLObjectOrInterface<#scalar, _>>::mark(); + } + }); + quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::marker::GraphQLInterface<#scalar> + for #ty #ty_generics #where_clause { fn mark() { #suppress_dead_code #all_impled_for_unique - #( <#implemented_for as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + #( { #mark_object_or_interface } )* } } } @@ -538,7 +597,7 @@ impl Definition { /// this [GraphQL interface][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -565,11 +624,30 @@ impl Definition { generics.replace_type_path_with_defaults(&mut ty); ty }); + let const_implements = self + .implements + .iter() + .cloned() + .map(|mut ty| { + generics.replace_type_path_with_defaults(&mut ty); + ty + }) + .collect::>(); + let transitive_checks = const_impl_for.clone().map(|const_impl_for| { + quote_spanned! { const_impl_for.span() => + ::juniper::assert_transitive_impls!( + #const_scalar, + #ty #ty_const_generics, + #const_impl_for, + #( #const_implements ),* + ); + } + }); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::marker::IsOutputType<#scalar> + for #ty #ty_generics #where_clause { fn mark() { @@ -577,9 +655,15 @@ impl Definition { #( #is_output )* ::juniper::assert_interfaces_impls!( #const_scalar, - #ty#ty_const_generics, + #ty #ty_const_generics, #( #const_impl_for ),* ); + ::juniper::assert_implemented_for!( + #const_scalar, + #ty #ty_const_generics, + #( #const_implements ),* + ); + #( #transitive_checks )* } } } @@ -589,7 +673,7 @@ impl Definition { /// [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -600,10 +684,7 @@ impl Definition { let (_, ty_generics, _) = self.generics.split_for_impl(); let name = &self.name; - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); + let description = &self.description; // Sorting is required to preserve/guarantee the order of implementers registered in schema. let mut implemented_for = self.implemented_for.clone(); @@ -612,12 +693,26 @@ impl Definition { a.cmp(&b) }); + // Sorting is required to preserve/guarantee the order of interfaces registered in schema. + let mut implements = self.implements.clone(); + implements.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); + let impl_interfaces = (!implements.is_empty()).then(|| { + quote! { + .interfaces(&[ + #( registry.get_type::<#implements>(info), )* + ]) + } + }); + let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::GraphQLType<#scalar> + for #ty #ty_generics #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { @@ -636,8 +731,9 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty#ty_generics>(info, &fields) + registry.build_interface_type::<#ty #ty_generics>(info, &fields) #description + #impl_interfaces .into_meta() } } @@ -648,7 +744,7 @@ impl Definition { /// [GraphQL interface][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -682,7 +778,7 @@ impl Definition { quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #ty_generics #where_clause { type Context = #context; @@ -730,7 +826,7 @@ impl Definition { /// [GraphQL interface][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -760,7 +856,7 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #ty_generics #where_clause { fn resolve_field_async<'b>( @@ -796,11 +892,12 @@ impl Definition { /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`Fields`]: juniper::macros::reflect::Fields /// [`WrappedType`]: juniper::macros::reflect::WrappedType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let implemented_for = &self.implemented_for; + let implements = &self.implements; let scalar = &self.scalar; let name = &self.name; let fields = self.fields.iter().map(|f| &f.name); @@ -811,16 +908,16 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ty #ty_generics #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ty #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[ @@ -830,16 +927,25 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::macros::reflect::Implements<#scalar> + for #ty #ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[#( <#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),*]; + } + + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ty #ty_generics #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> - for #ty#ty_generics + impl #impl_generics ::juniper::macros::reflect::Fields<#scalar> + for #ty #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; @@ -851,7 +957,7 @@ impl Definition { /// [GraphQL interface][1]. /// /// [`FieldMeta`]: juniper::macros::reflect::FieldMeta - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_field_meta_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; @@ -881,10 +987,10 @@ impl Definition { quote! { #[allow(non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::FieldMeta< + impl #impl_generics ::juniper::macros::reflect::FieldMeta< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { + > for #ty #ty_generics #where_clause { type Context = #context; type TypeInfo = (); const TYPE: ::juniper::macros::reflect::Type = @@ -912,7 +1018,7 @@ impl Definition { /// this [GraphQL interface][1]. /// /// [`Field`]: juniper::macros::reflect::Field - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; @@ -955,10 +1061,10 @@ impl Definition { quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::Field< + impl #impl_generics ::juniper::macros::reflect::Field< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { + > for #ty #ty_generics #where_clause { fn call( &self, info: &Self::TypeInfo, @@ -968,7 +1074,7 @@ impl Definition { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( - #ty#const_ty_generics, + #ty #const_ty_generics, #const_implemented_for, #const_scalar, #field_name, @@ -992,7 +1098,7 @@ impl Definition { /// of this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflect::AsyncField - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_async_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; @@ -1035,10 +1141,10 @@ impl Definition { quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::AsyncField< + impl #impl_generics ::juniper::macros::reflect::AsyncField< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { + > for #ty #ty_generics #where_clause { fn call<'b>( &'b self, info: &'b Self::TypeInfo, @@ -1048,7 +1154,7 @@ impl Definition { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( - #ty#const_ty_generics, + #ty #const_ty_generics, #const_implemented_for, #const_scalar, #field_name, @@ -1240,14 +1346,14 @@ impl Definition { let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); + lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ty = &self.enum_alias_ident; let (_, ty_generics, _) = generics.split_for_impl(); - quote! { for<#( #lifetimes ),*> #ty#ty_generics } + quote! { for<#( #lifetimes ),*> #ty #ty_generics } } else { quote! { Self } }; diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index 34a70c21..37b47d16 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -6,20 +6,16 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; -use crate::{ - common::{ - field, - parse::{self, TypeExt as _}, - scalar, - }, - result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer, RenameRule}, +use crate::common::{ + diagnostic, field, + parse::{self, TypeExt as _}, + path_eq_single, rename, scalar, SpanContainer, }; use super::{Attr, Definition, Query}; -/// [`GraphQLScope`] of errors for `#[graphql_object]` macro. -const ERR: GraphQLScope = GraphQLScope::ObjectAttr; +/// [`diagnostic::Scope`] of errors for `#[graphql_object]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::ObjectAttr; /// Expands `#[graphql_object]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { @@ -73,7 +69,7 @@ where .rename_fields .as_deref() .copied() - .unwrap_or(RenameRule::CamelCase); + .unwrap_or(rename::Policy::CamelCase); let async_only = TypeId::of::() != TypeId::of::(); let fields: Vec<_> = ast @@ -143,7 +139,7 @@ where fn parse_field( method: &mut syn::ImplItemMethod, async_only: bool, - renaming: &RenameRule, + renaming: &rename::Policy, ) -> Option { let method_attrs = method.attrs.clone(); @@ -192,7 +188,7 @@ fn parse_field( } syn::FnArg::Typed(arg) => { if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() == "self" { + if a.ident == "self" { return err_invalid_method_receiver(arg); } } @@ -216,17 +212,11 @@ fn parse_field( }; ty.lifetimes_anonymized(); - let description = attr.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = attr - .deprecated - .as_deref() - .map(|d| d.as_ref().map(syn::LitStr::value)); - Some(field::Definition { name, ty, - description, - deprecated, + description: attr.description.map(SpanContainer::into_inner), + deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 7cbe961b..189ed261 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -7,16 +7,12 @@ use proc_macro_error::ResultExt as _; use quote::ToTokens; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; -use crate::{ - common::{field, parse::TypeExt as _, scalar}, - result::GraphQLScope, - util::{span_container::SpanContainer, RenameRule}, -}; +use crate::common::{diagnostic, field, parse::TypeExt as _, rename, scalar, SpanContainer}; use super::{Attr, Definition, Query}; -/// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro. -const ERR: GraphQLScope = GraphQLScope::ObjectDerive; +/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLObject)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::ObjectDerive; /// Expands `#[derive(GraphQLObject)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { @@ -38,7 +34,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { let struct_ident = ast.ident; let (_, struct_generics, _) = ast.generics.split_for_impl(); - let ty = parse_quote! { #struct_ident#struct_generics }; + let ty = parse_quote! { #struct_ident #struct_generics }; let name = attr .name @@ -62,7 +58,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { .rename_fields .as_deref() .copied() - .unwrap_or(RenameRule::CamelCase); + .unwrap_or(rename::Policy::CamelCase); let mut fields = vec![]; if let syn::Data::Struct(data) = &ast.data { @@ -112,7 +108,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { /// /// Returns [`None`] if parsing fails, or the struct field is ignored. #[must_use] -fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { +fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option { let attr = field::Attr::from_attrs("graphql", &field.attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; @@ -141,17 +137,11 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option>, /// Explicitly specified [description][2] of this [GraphQL object][1] type. /// - /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + pub(crate) description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL object][1] type with. @@ -56,7 +54,7 @@ pub(crate) struct Attr { /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) context: Option>, /// Explicitly specified type (or type parameter with its bounds) of @@ -70,23 +68,24 @@ pub(crate) struct Attr { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) scalar: Option>, /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub(crate) interfaces: HashSet>, - /// Explicitly specified [`RenameRule`] for all fields of this + /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL object][1] type. /// - /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. + /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by + /// default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) rename_fields: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Objects + pub(crate) rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. @@ -112,13 +111,9 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { @@ -194,7 +189,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { - attr.description = get_doc_comment(attrs); + attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) @@ -203,38 +198,38 @@ impl Attr { /// Definition of [GraphQL object][1] for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Objects #[derive(Debug)] pub(crate) struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) name: String, /// Rust type that this [GraphQL object][1] is represented with. /// /// It should contain all its generics, if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) ty: syn::Type, /// Generics of the Rust type that this [GraphQL object][1] is implemented /// for. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) generics: syn::Generics, /// Description of this [GraphQL object][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) description: Option, + /// [1]: https://spec.graphql.org/October2021#sec-Objects + pub(crate) description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL object][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] @@ -242,28 +237,28 @@ pub(crate) struct Definition { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) fields: Vec, /// [GraphQL interfaces][2] implemented by this [GraphQL object][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub(crate) interfaces: HashSet, /// [GraphQL operation][1] this [`Definition`] should generate code for. /// /// Either [GraphQL query][2] or [GraphQL subscription][3]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Operations - /// [2]: https://spec.graphql.org/June2018/#sec-Query - /// [3]: https://spec.graphql.org/June2018/#sec-Subscription + /// [1]: https://spec.graphql.org/October2021#sec-Language.Operations + /// [2]: https://spec.graphql.org/October2021#sec-Query + /// [3]: https://spec.graphql.org/October2021#sec-Subscription pub(crate) _operation: PhantomData>, } @@ -276,7 +271,7 @@ impl Definition { /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { let mut generics = self.generics.clone(); @@ -304,7 +299,7 @@ impl Definition { let mut ty = self.ty.clone(); ty.lifetimes_iter_mut(&mut |lt| { let ident = lt.ident.unraw(); - lt.ident = format_ident!("__fa__{}", ident); + lt.ident = format_ident!("__fa__{ident}"); lifetimes.push(lt.clone()); }); @@ -333,7 +328,7 @@ impl Definition { /// this [GraphQL object][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -351,7 +346,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { fn mark() { #( #fields_marks )* @@ -368,7 +363,7 @@ impl Definition { /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`Fields`]: juniper::macros::reflect::Fields /// [`WrappedType`]: juniper::macros::reflect::WrappedType - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -380,7 +375,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { @@ -388,7 +383,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { @@ -397,7 +392,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::Implements<#scalar> + impl #impl_generics ::juniper::macros::reflect::Implements<#scalar> for #ty #where_clause { @@ -406,7 +401,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { @@ -414,7 +409,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> + impl #impl_generics ::juniper::macros::reflect::Fields<#scalar> for #ty #where_clause { @@ -427,7 +422,7 @@ impl Definition { /// [GraphQL object][1]. /// /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -436,16 +431,13 @@ impl Definition { let ty = &self.ty; let name = &self.name; - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); + let description = &self.description; let extract_stream_type = TypeId::of::() != TypeId::of::(); let fields_meta = self .fields .iter() - .map(|f| f.method_meta_tokens(extract_stream_type.then(|| scalar))); + .map(|f| f.method_meta_tokens(extract_stream_type.then_some(scalar))); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. let mut interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -463,7 +455,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -490,7 +482,7 @@ impl Definition { /// [GraphQL query operation][2] of the [`Definition`] to generate code for. /// -/// [2]: https://spec.graphql.org/June2018/#sec-Query +/// [2]: https://spec.graphql.org/October2021#sec-Query struct Query; impl ToTokens for Definition { @@ -512,7 +504,7 @@ impl Definition { /// [GraphQL object][1]. /// /// [`GraphQLObject`]: juniper::GraphQLObject - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -544,7 +536,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #where_clause { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* @@ -560,7 +552,7 @@ impl Definition { /// of this [GraphQL object][1]. /// /// [`FieldMeta`]: juniper::FieldMeta - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_field_meta_tokens(&self) -> TokenStream { let impl_ty = &self.ty; @@ -621,7 +613,7 @@ impl Definition { /// this [GraphQL object][1]. /// /// [`Field`]: juniper::Field - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_field_tokens(&self) -> TokenStream { let (impl_ty, scalar) = (&self.ty, &self.scalar); @@ -694,7 +686,7 @@ impl Definition { /// of this [GraphQL object][1]. /// /// [`AsyncField`]: juniper::AsyncField - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_async_field_tokens(&self) -> TokenStream { let (impl_ty, scalar) = (&self.ty, &self.scalar); @@ -756,7 +748,7 @@ impl Definition { /// [GraphQL object][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -786,7 +778,7 @@ impl Definition { quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); @@ -813,7 +805,7 @@ impl Definition { _: &Self::Context, _: &Self::TypeInfo, ) -> String { - #name.to_string() + #name.into() } } } @@ -823,7 +815,7 @@ impl Definition { /// [GraphQL object][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -850,7 +842,7 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_field_async<'b>( &'b self, diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index b9ff5b58..90024820 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -4,15 +4,12 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{parse_quote, spanned::Spanned}; -use crate::{ - common::{parse, scalar}, - graphql_scalar::TypeOrIdent, - GraphQLScope, -}; +use crate::common::{diagnostic, parse, scalar, SpanContainer}; -use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken}; +use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken, TypeOrIdent}; -const ERR: GraphQLScope = GraphQLScope::ScalarAttr; +/// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr; /// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { @@ -58,11 +55,10 @@ fn expand_on_type_alias( methods, name: attr .name - .as_deref() - .cloned() + .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), - description: attr.description.as_deref().cloned(), - specified_by_url: attr.specified_by_url.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), + specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, }; @@ -90,11 +86,10 @@ fn expand_on_derive_input( methods, name: attr .name - .as_deref() - .cloned() + .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), - description: attr.description.as_deref().cloned(), - specified_by_url: attr.specified_by_url.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), + specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, }; diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index e16bcd7d..ff4f93b1 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -4,12 +4,12 @@ use proc_macro2::TokenStream; use quote::ToTokens; use syn::{parse_quote, spanned::Spanned}; -use crate::{common::scalar, result::GraphQLScope}; +use crate::common::{diagnostic, scalar, SpanContainer}; use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent}; -/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro. -const ERR: GraphQLScope = GraphQLScope::ScalarDerive; +/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive; /// Expands `#[derive(GraphQLScalar)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { @@ -27,11 +27,10 @@ pub fn expand(input: TokenStream) -> syn::Result { methods, name: attr .name - .as_deref() - .cloned() + .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), - description: attr.description.as_deref().cloned(), - specified_by_url: attr.specified_by_url.as_deref().cloned(), + description: attr.description.map(SpanContainer::into_inner), + specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, } .to_token_stream()) diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 55652980..2280d9c2 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -14,15 +14,13 @@ use syn::{ }; use url::Url; -use crate::{ - common::{ - parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, - }, - scalar, +use crate::common::{ + filter_attrs, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, }, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, + scalar, Description, SpanContainer, }; pub mod attr; @@ -42,7 +40,7 @@ struct Attr { /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars - description: Option>, + description: Option>, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// @@ -112,20 +110,16 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "specified_by_url" => { input.parse::()?; let lit = input.parse::()?; let url = lit.value().parse::().map_err(|err| { - syn::Error::new(lit.span(), format!("Invalid URL: {}", err)) + syn::Error::new(lit.span(), format!("Invalid URL: {err}")) })?; out.specified_by_url .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) @@ -255,7 +249,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { - attr.description = get_doc_comment(attrs); + attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) @@ -304,7 +298,7 @@ struct Definition { /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars - description: Option, + description: Option, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// @@ -349,11 +343,11 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ty + impl #impl_gens ::juniper::marker::IsInputType<#scalar> for #ty #where_clause { } #[automatically_derived] - impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty + impl #impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { } } } @@ -365,12 +359,9 @@ impl Definition { /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let name = &self.name; - let description = self - .description - .as_ref() - .map(|val| quote! { .description(#val) }); + let name = &self.name; + let description = &self.description; let specified_by_url = self.specified_by_url.as_ref().map(|url| { let url_lit = url.as_str(); quote! { .specified_by_url(#url_lit) } @@ -381,7 +372,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty + impl #impl_gens ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name(_: &Self::TypeInfo) -> Option<&'static str> { @@ -419,7 +410,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ty + impl #impl_gens ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = (); @@ -454,7 +445,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty + impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_async<'b>( @@ -486,7 +477,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::ToInputValue<#scalar> for #ty + impl #impl_gens ::juniper::ToInputValue<#scalar> for #ty #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { @@ -511,7 +502,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty + impl #impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { type Error = ::juniper::executor::FieldError<#scalar>; @@ -539,12 +530,12 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty + impl #impl_gens ::juniper::ParseScalarValue<#scalar> for #ty #where_clause { fn from_str( token: ::juniper::parser::ScalarToken<'_>, - ) -> ::juniper::ParseScalarResult<'_, #scalar> { + ) -> ::juniper::ParseScalarResult<#scalar> { #from_str } } @@ -567,14 +558,14 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty + impl #impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty + impl #impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Types = @@ -582,7 +573,7 @@ impl Definition { } #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty + impl #impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; @@ -606,7 +597,7 @@ impl Definition { TypeOrIdent::Type(ty) => ty.into_token_stream(), TypeOrIdent::Ident(ident) => { let (_, ty_gen, _) = self.generics.split_for_impl(); - quote! { #ident#ty_gen } + quote! { #ident #ty_gen } } }; @@ -644,7 +635,7 @@ impl Definition { } TypeOrIdent::Ident(ident) => { let (_, ty_gens, _) = generics.split_for_impl(); - quote! { #ident#ty_gens } + quote! { #ident #ty_gens } } }; diff --git a/juniper_codegen/src/graphql_subscription/mod.rs b/juniper_codegen/src/graphql_subscription/mod.rs index 3c07c9ae..c8d2fb42 100644 --- a/juniper_codegen/src/graphql_subscription/mod.rs +++ b/juniper_codegen/src/graphql_subscription/mod.rs @@ -1,6 +1,6 @@ //! Code generation for [GraphQL subscription][1]. //! -//! [1]: https://spec.graphql.org/June2018/#sec-Subscription +//! [1]: https://spec.graphql.org/October2021#sec-Subscription pub mod attr; @@ -13,7 +13,7 @@ use crate::{common::field, graphql_object::Definition}; /// [GraphQL subscription operation][2] of the [`Definition`] to generate code /// for. /// -/// [2]: https://spec.graphql.org/June2018/#sec-Subscription +/// [2]: https://spec.graphql.org/October2021#sec-Subscription struct Subscription; impl ToTokens for Definition { @@ -31,7 +31,7 @@ impl Definition { /// [GraphQL subscription][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Subscription + /// [1]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -44,7 +44,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); @@ -70,7 +70,7 @@ impl Definition { _: &Self::Context, _: &Self::TypeInfo, ) -> String { - #name.to_string() + #name.into() } } } @@ -80,7 +80,7 @@ impl Definition { /// for this [GraphQL subscription][1]. /// /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue - /// [1]: https://spec.graphql.org/June2018/#sec-Subscription + /// [1]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] fn impl_graphql_subscription_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -109,7 +109,7 @@ impl Definition { quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #where_clause + impl #impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #where_clause { fn resolve_field_into_stream< 's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f, diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index ae2be4db..006ad226 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -6,19 +6,15 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; -use crate::{ - common::{parse, scalar}, - result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer}, -}; +use crate::common::{diagnostic, parse, path_eq_single, scalar, SpanContainer}; use super::{ all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, VariantDefinition, }; -/// [`GraphQLScope`] of errors for `#[graphql_union]` macro. -const ERR: GraphQLScope = GraphQLScope::UnionAttr; +/// [`diagnostic::Scope`] of errors for `#[graphql_union]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::UnionAttr; /// Expands `#[graphql_union]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { @@ -112,7 +108,7 @@ fn expand_on_trait( /// On failure returns [`None`] and internally fills up [`proc_macro_error`] /// with the corresponding errors. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions fn parse_variant_from_trait_method( method: &mut syn::TraitItemMethod, trait_ident: &syn::Ident, @@ -177,10 +173,9 @@ fn parse_variant_from_trait_method( ERR.custom( method_span, format!( - "trait method `{}` conflicts with the external resolver \ - function `{}` declared on the trait to resolve the \ - variant type `{}`", - method_ident, + "trait method `{method_ident}` conflicts with the external \ + resolver function `{}` declared on the trait to resolve \ + the variant type `{}`", other.to_token_stream(), ty.to_token_stream(), ), diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index 1c1bc5c1..a10be60c 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -5,19 +5,15 @@ use proc_macro_error::ResultExt as _; use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; -use crate::{ - common::{parse::TypeExt as _, scalar}, - result::GraphQLScope, - util::span_container::SpanContainer, -}; +use crate::common::{diagnostic, parse::TypeExt as _, scalar, SpanContainer}; use super::{ all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, VariantDefinition, }; -/// [`GraphQLScope`] of errors for `#[derive(GraphQLUnion)]` macro. -const ERR: GraphQLScope = GraphQLScope::UnionDerive; +/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLUnion)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::UnionDerive; /// Expands `#[derive(GraphQLUnion)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { @@ -98,7 +94,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { /// On failure returns [`None`] and internally fills up [`proc_macro_error`] /// with the corresponding errors. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions fn parse_variant_from_enum_variant( var: syn::Variant, enum_ident: &syn::Ident, diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 21fb9cb4..fa585b00 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -1,6 +1,6 @@ //! Code generation for [GraphQL union][1]. //! -//! [1]: https://spec.graphql.org/June2018/#sec-Unions +//! [1]: https://spec.graphql.org/October2021#sec-Unions pub mod attr; pub mod derive; @@ -17,16 +17,13 @@ use syn::{ token, }; -use crate::{ - common::{ - gen, - parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, - }, - scalar, +use crate::common::{ + filter_attrs, gen, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, }, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, + scalar, Description, SpanContainer, }; /// Helper alias for the type of [`Attr::external_resolvers`] field. @@ -35,23 +32,24 @@ type AttrResolvers = HashMap>; /// Available arguments behind `#[graphql]` (or `#[graphql_union]`) attribute /// when generating code for [GraphQL union][1] type. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct Attr { /// Explicitly specified name of [GraphQL union][1] type. /// /// If [`None`], then Rust type name is used by default. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions name: Option>, /// Explicitly specified [description][2] of [GraphQL union][1] type. /// - /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - description: Option>, + /// [1]: https://spec.graphql.org/October2021#sec-Unions + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL union][1] type with. @@ -59,7 +57,7 @@ struct Attr { /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions context: Option>, /// Explicitly specified type of [`ScalarValue`] to use for resolving this @@ -73,7 +71,7 @@ struct Attr { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: Option>, /// Explicitly specified external resolver functions for [GraphQL union][1] @@ -84,7 +82,7 @@ struct Attr { /// external resolver function has sense, when some custom [union][1] /// variant resolving logic is involved, or variants cannot be inferred. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions external_resolvers: AttrResolvers, /// Indicator whether the generated code is intended to be used only inside @@ -111,13 +109,9 @@ impl Parse for Attr { } "desc" | "description" => { input.parse::()?; - let desc = input.parse::()?; + let desc = input.parse::()?; out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { @@ -181,7 +175,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if meta.description.is_none() { - meta.description = get_doc_comment(attrs); + meta.description = Description::parse_from_doc_attrs(attrs)?; } Ok(meta) @@ -191,13 +185,13 @@ impl Attr { /// Available arguments behind `#[graphql]` attribute when generating code for /// [GraphQL union][1]'s variant. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct VariantAttr { /// Explicitly specified marker for the variant/field being ignored and not /// included into [GraphQL union][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions ignore: Option>, /// Explicitly specified external resolver function for this [GraphQL union][1] variant. @@ -206,7 +200,7 @@ struct VariantAttr { /// Usually, specifying an external resolver function has sense, when some custom resolving /// logic is involved. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions external_resolver: Option>, } @@ -258,22 +252,22 @@ impl VariantAttr { /// Definition of [GraphQL union][1] for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions struct Definition { /// Name of this [GraphQL union][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions name: String, /// Rust type that this [GraphQL union][1] is represented with. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions ty: syn::Type, /// Generics of the Rust type that this [GraphQL union][1] is implemented /// for. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions generics: syn::Generics, /// Indicator whether code should be generated for a trait object, rather @@ -282,15 +276,15 @@ struct Definition { /// Description of this [GraphQL union][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - description: Option, + /// [1]: https://spec.graphql.org/October2021#sec-Unions + description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL union][1]. /// /// [`Context`]: juniper::Context /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions context: syn::Type, /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation @@ -304,12 +298,12 @@ struct Definition { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: scalar::Type, /// Variants definitions of this [GraphQL union][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions variants: Vec, } @@ -333,7 +327,7 @@ impl Definition { /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_generics( &self, @@ -342,7 +336,7 @@ impl Definition { let (_, ty_generics, _) = self.generics.split_for_impl(); let ty = &self.ty; - let mut ty_full = quote! { #ty#ty_generics }; + let mut ty_full = quote! { #ty #ty_generics }; if self.is_trait_object { ty_full = quote! { dyn #ty_full + '__obj + Send + Sync }; } @@ -374,14 +368,14 @@ impl Definition { let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); + lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ty = &self.ty; let (_, ty_generics, _) = generics.split_for_impl(); - quote! { for<#( #lifetimes ),*> #ty#ty_generics } + quote! { for<#( #lifetimes ),*> #ty #ty_generics } } else { quote! { Self } }; @@ -410,7 +404,7 @@ impl Definition { /// [GraphQL union][1]. /// /// [`GraphQLUnion`]: juniper::GraphQLUnion - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_union_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -424,7 +418,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause + impl #impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause { fn mark() { #all_variants_unique @@ -438,7 +432,7 @@ impl Definition { /// this [GraphQL union][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -449,7 +443,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause + impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause { fn mark() { #( <#variant_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* @@ -462,7 +456,7 @@ impl Definition { /// [GraphQL union][1]. /// /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -470,16 +464,13 @@ impl Definition { let (impl_generics, ty_full, where_clause) = self.impl_generics(false); let name = &self.name; - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); + let description = &self.description; let variant_tys = self.variants.iter().map(|var| &var.ty); quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause + impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -506,7 +497,7 @@ impl Definition { /// [GraphQL union][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -528,7 +519,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause + impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause { type Context = #context; type TypeInfo = (); @@ -543,7 +534,7 @@ impl Definition { info: &Self::TypeInfo, ) -> String { #( #match_variant_names )* - panic!( + ::std::panic!( "GraphQL union `{}` cannot be resolved into any of its \ variants in its current state", #name, @@ -559,7 +550,7 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { let context = executor.context(); #( #variant_resolvers )* - return Err(::juniper::FieldError::from(format!( + return Err(::juniper::FieldError::from(::std::format!( "Concrete type `{}` is not handled by instance \ resolvers on GraphQL union `{}`", type_name, #name, @@ -573,7 +564,7 @@ impl Definition { /// [GraphQL union][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -590,7 +581,7 @@ impl Definition { quote! { #[allow(non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause + impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause { fn resolve_into_type_async<'b>( &'b self, @@ -601,7 +592,7 @@ impl Definition { ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let context = executor.context(); #( #variant_async_resolvers )* - return ::juniper::macros::helper::err_fut(format!( + return ::juniper::macros::helper::err_fut(::std::format!( "Concrete type `{}` is not handled by instance \ resolvers on GraphQL union `{}`", type_name, #name, @@ -617,7 +608,7 @@ impl Definition { /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`WrappedType`]: juniper::macros::reflect::WrappedType - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -627,7 +618,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { @@ -635,7 +626,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { @@ -646,7 +637,7 @@ impl Definition { } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { @@ -658,22 +649,22 @@ impl Definition { /// Definition of [GraphQL union][1] variant for code generation. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions struct VariantDefinition { /// Rust type that this [GraphQL union][1] variant resolves into. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions ty: syn::Type, /// Rust code for value resolution of this [GraphQL union][1] variant. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_code: syn::Expr, /// Rust code for checking whether [GraphQL union][1] should be resolved /// into this variant. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_check: syn::Expr, /// Rust type of [`Context`] that this [GraphQL union][1] variant requires @@ -683,7 +674,7 @@ struct VariantDefinition { /// trait method contains context argument. /// /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/October2021#sec-Unions context: Option, } @@ -762,7 +753,7 @@ impl VariantDefinition { /// If duplication happens, then resolving code is overwritten with the one from /// `external_resolvers`. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions fn emerge_union_variants_from_attr( variants: &mut Vec, external_resolvers: AttrResolvers, @@ -809,7 +800,7 @@ fn emerge_union_variants_from_attr( /// used to enforce this requirement in the generated code. However, due to the /// bad error message this implementation should stay and provide guidance. /// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: juniper::sa::assert_type_ne_all fn all_variants_different(variants: &[VariantDefinition]) -> bool { let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect(); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1c7da4db..d88488c3 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -1,9 +1,6 @@ #![doc = include_str!("../README.md")] #![recursion_limit = "1024"] -mod result; -mod util; - // NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust // doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, // and so we cannot move the definition into a sub-module and use the `#[macro_export]` @@ -16,8 +13,8 @@ mod util; /// By default, [`SpanContainer::span_ident`] is used. /// /// [`Span`]: proc_macro2::Span -/// [`SpanContainer`]: crate::util::span_container::SpanContainer -/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +/// [`SpanContainer`]: crate::common::SpanContainer +/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_opt { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if let Some(v) = $self.$field { @@ -47,8 +44,8 @@ macro_rules! try_merge_opt { /// /// [`HashMap`]: std::collections::HashMap /// [`Span`]: proc_macro2::Span -/// [`SpanContainer`]: crate::util::span_container::SpanContainer -/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +/// [`SpanContainer`]: crate::common::SpanContainer +/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_hashmap { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if !$self.$field.is_empty() { @@ -80,8 +77,8 @@ macro_rules! try_merge_hashmap { /// /// [`HashSet`]: std::collections::HashSet /// [`Span`]: proc_macro2::Span -/// [`SpanContainer`]: crate::util::span_container::SpanContainer -/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +/// [`SpanContainer`]: crate::common::SpanContainer +/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_hashset { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if !$self.$field.is_empty() { @@ -100,10 +97,9 @@ macro_rules! try_merge_hashset { }; } -mod derive_enum; -mod derive_input_object; - mod common; +mod graphql_enum; +mod graphql_input_object; mod graphql_interface; mod graphql_object; mod graphql_scalar; @@ -113,28 +109,245 @@ mod scalar_value; use proc_macro::TokenStream; use proc_macro_error::{proc_macro_error, ResultExt as _}; -use result::GraphQLScope; - -#[proc_macro_error] -#[proc_macro_derive(GraphQLEnum, attributes(graphql))] -pub fn derive_enum(input: TokenStream) -> TokenStream { - let ast = syn::parse::(input).unwrap(); - let gen = derive_enum::impl_enum(ast, GraphQLScope::DeriveEnum); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } -} +/// `#[derive(GraphQLInputObject)]` macro for deriving a +/// [GraphQL input object][0] implementation for a Rust struct. Each +/// non-ignored field type must itself be [GraphQL input object][0] or a +/// [GraphQL scalar][2]. +/// +/// The `#[graphql]` helper attribute is used for configuring the derived +/// implementation. Specifying multiple `#[graphql]` attributes on the same +/// definition is totally okay. They all will be treated as a single attribute. +/// +/// ```rust +/// use juniper::GraphQLInputObject; +/// +/// #[derive(GraphQLInputObject)] +/// struct Point2D { +/// x: f64, +/// y: f64, +/// } +/// ``` +/// +/// # Custom name and description +/// +/// The name of a [GraphQL input object][0] or its [fields][1] may be overridden +/// with the `name` attribute's argument. By default, a type name or a struct +/// field name is used in a `camelCase`. +/// +/// The description of a [GraphQL input object][0] or its [fields][1] may be +/// specified either with the `description`/`desc` attribute's argument, or with +/// a regular Rust doc comment. +/// +/// ```rust +/// # use juniper::GraphQLInputObject; +/// # +/// #[derive(GraphQLInputObject)] +/// #[graphql( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "Point", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "A point is the simplest two-dimensional primitive.", +/// )] +/// struct Point2D { +/// /// Abscissa value. +/// x: f64, +/// +/// #[graphql(name = "y", desc = "Ordinate value")] +/// y_coord: f64, +/// } +/// ``` +/// +/// # Renaming policy +/// +/// By default, all [GraphQL input object fields][1] are renamed in a +/// `camelCase` manner (so a `y_coord` Rust struct field becomes a +/// `yCoord` [value][1] in GraphQL schema, and so on). This complies with +/// default GraphQL naming conventions as [demonstrated in spec][0]. +/// +/// However, if you need for some reason another naming convention, it's +/// possible to do so by using the `rename_all` attribute's argument. At the +/// moment, it supports the following policies only: `SCREAMING_SNAKE_CASE`, +/// `camelCase`, `none` (disables any renaming). +/// +/// ```rust +/// # use juniper::GraphQLInputObject; +/// # +/// #[derive(GraphQLInputObject)] +/// #[graphql(rename_all = "none")] // disables renaming +/// struct Point2D { +/// x: f64, +/// y_coord: f64, // will be `y_coord` instead of `yCoord` in GraphQL schema +/// } +/// ``` +/// +/// # Ignoring fields +/// +/// To omit exposing a Rust field in a GraphQL schema, use the `ignore` +/// attribute's argument directly on that field. Ignored fields must implement +/// [`Default`] or have the `default = ` attribute's argument. +/// +/// ```rust +/// # use juniper::GraphQLInputObject; +/// # +/// enum System { +/// Cartesian, +/// } +/// +/// #[derive(GraphQLInputObject)] +/// struct Point2D { +/// x: f64, +/// y: f64, +/// #[graphql(ignore)] +/// shift: f64, // `Default::default()` impl is used. +/// #[graphql(skip, default = System::Cartesian)] +/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +/// // This attribute is required, as we need to be to construct `Point2D` +/// // from `{ x: 0.0, y: 0.0 }` GraphQL input. +/// system: System, +/// } +/// ``` +/// +/// [`ScalarValue`]: juniper::ScalarValue +/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects +/// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition +/// [2]: https://spec.graphql.org/October2021#sec-Scalars #[proc_macro_error] #[proc_macro_derive(GraphQLInputObject, attributes(graphql))] pub fn derive_input_object(input: TokenStream) -> TokenStream { - let ast = syn::parse::(input).unwrap(); - let gen = derive_input_object::impl_input_object(ast, GraphQLScope::DeriveInputObject); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } + graphql_input_object::derive::expand(input.into()) + .unwrap_or_abort() + .into() +} + +/// `#[derive(GraphQLEnum)]` macro for deriving a [GraphQL enum][0] +/// implementation for Rust enums. +/// +/// The `#[graphql]` helper attribute is used for configuring the derived +/// implementation. Specifying multiple `#[graphql]` attributes on the same +/// definition is totally okay. They all will be treated as a single attribute. +/// +/// ```rust +/// use juniper::GraphQLEnum; +/// +/// #[derive(GraphQLEnum)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// # Custom name, description and deprecation +/// +/// The name of a [GraphQL enum][0] or its [values][1] may be overridden with +/// the `name` attribute's argument. By default, a type name is used or a +/// variant name in `SCREAMING_SNAKE_CASE`. +/// +/// The description of a [GraphQL enum][0] or its [values][1] may be specified +/// either with the `description`/`desc` attribute's argument, or with a regular +/// Rust doc comment. +/// +/// [GraphQL enum value][1] may be deprecated by specifying the `deprecated` +/// attribute's argument, or with regular a Rust `#[deprecated]` attribute. +/// +/// ```rust +/// # #![allow(deprecated)] +/// # +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "AvailableEpisodes", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "Possible episodes.", +/// )] +/// enum Episode { +/// /// Doc comment, also acting as description. +/// #[deprecated(note = "Don't use it")] +/// NewHope, +/// +/// #[graphql(name = "Jedi", desc = "Arguably the best one in the trilogy")] +/// #[graphql(deprecated = "Don't use it")] +/// Jedai, +/// +/// Empire, +/// } +/// ``` +/// +/// # Renaming policy +/// +/// By default, all [GraphQL enum values][1] are renamed in a +/// `SCREAMING_SNAKE_CASE` manner (so a `NewHope` Rust enum variant becomes a +/// `NEW_HOPE` [value][1] in GraphQL schema, and so on). This complies with +/// default GraphQL naming conventions as [demonstrated in spec][0]. +/// +/// However, if you need for some reason another naming convention, it's +/// possible to do so by using the `rename_all` attribute's argument. At the +/// moment, it supports the following policies only: `SCREAMING_SNAKE_CASE`, +/// `camelCase`, `none` (disables any renaming). +/// +/// ```rust +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql(rename_all = "none")] // disables renaming +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// # Ignoring enum variants +/// +/// To omit exposing a Rust enum variant in a GraphQL schema, use the `ignore` +/// attribute's argument directly on that variant. Only ignored Rust enum +/// variants are allowed to contain fields. +/// +/// ```rust +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// #[graphql(ignore)] +/// Legends(T), +/// } +/// ``` +/// +/// # Custom `ScalarValue` +/// +/// By default, `#[derive(GraphQLEnum)]` macro generates code, which is generic +/// over a [`ScalarValue`] type. This can be changed with the `scalar` +/// attribute's argument. +/// +/// ```rust +/// # use juniper::{DefaultScalarValue, GraphQLEnum}; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql(scalar = DefaultScalarValue)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// [`ScalarValue`]: juniper::ScalarValue +/// [0]: https://spec.graphql.org/October2021#sec-Enums +/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value +#[proc_macro_error] +#[proc_macro_derive(GraphQLEnum, attributes(graphql))] +pub fn derive_enum(input: TokenStream) -> TokenStream { + graphql_enum::derive::expand(input.into()) + .unwrap_or_abort() + .into() } /// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][0] @@ -227,18 +440,17 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// ) -> Result { /// // ^^^^^^ must implement `IntoFieldError` /// input.as_string_value() -/// .ok_or_else(|| format!("Expected `String`, found: {}", input)) +/// .ok_or_else(|| format!("Expected `String`, found: {input}")) /// .and_then(|str| { /// str.strip_prefix("id: ") /// .ok_or_else(|| { /// format!( /// "Expected `UserId` to begin with `id: `, \ -/// found: {}", -/// input, +/// found: {input}", /// ) /// }) /// }) -/// .map(|id| Self(id.to_owned())) +/// .map(|id| Self(id.into())) /// } /// } /// ``` @@ -270,19 +482,19 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// fn to_output(v: &StringOrInt) -> Value { /// match v { -/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// /// fn from_input(v: &InputValue) -> Result { /// v.as_string_value() -/// .map(|s| StringOrInt::String(s.to_owned())) +/// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// -/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { /// >::from_str(value) /// .or_else(|_| >::from_str(value)) /// } @@ -313,19 +525,19 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// pub(super) fn to_output(v: &StringOrInt) -> Value { /// match v { -/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// /// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() -/// .map(|s| StringOrInt::String(s.to_owned())) +/// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// -/// pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +/// pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { /// >::from_str(t) /// .or_else(|_| >::from_str(t)) /// } @@ -351,7 +563,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// impl StringOrInt { /// fn to_output(&self) -> Value { /// match self { -/// Self::String(str) => Value::scalar(str.to_owned()), +/// Self::String(s) => Value::scalar(s.to_owned()), /// Self::Int(i) => Value::scalar(*i), /// } /// } @@ -361,12 +573,12 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// S: ScalarValue /// { /// v.as_string_value() -/// .map(|s| Self::String(s.to_owned())) +/// .map(|s| Self::String(s.into())) /// .or_else(|| v.as_int_value().map(Self::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// -/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult /// where /// S: ScalarValue /// { @@ -403,7 +615,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// S: ScalarValue, /// { /// match v { -/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } @@ -413,9 +625,9 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// S: ScalarValue, /// { /// v.as_string_value() -/// .map(|s| StringOrInt::String(s.to_owned())) +/// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// /// // No need in `parse_token()` function. @@ -523,8 +735,8 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// /// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() -/// .ok_or_else(|| format!("Expected `String`, found: {}", v)) -/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) +/// .ok_or_else(|| format!("Expected `String`, found: {v}")) +/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) /// } /// } /// # @@ -553,7 +765,7 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// methods). /// /// ```rust -/// # use std::{fmt, convert::TryInto as _}; +/// # use std::fmt; /// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; /// # use juniper::ScalarValue; @@ -757,6 +969,125 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// } /// ``` /// +/// # Interfaces implementing other interfaces +/// +/// GraphQL allows implementing interfaces on other interfaces in addition to +/// objects. +/// +/// > __NOTE:__ Every interface has to specify all other interfaces/objects it +/// > implements or is implemented for. Missing one of `for = ` or +/// > `impl = ` attributes is an understandable compile-time error. +/// +/// ```rust +/// # extern crate juniper; +/// use juniper::{graphql_interface, graphql_object, ID}; +/// +/// #[graphql_interface(for = [HumanValue, Luke])] +/// struct Node { +/// id: ID, +/// } +/// +/// #[graphql_interface(impl = NodeValue, for = Luke)] +/// struct Human { +/// id: ID, +/// home_planet: String, +/// } +/// +/// struct Luke { +/// id: ID, +/// } +/// +/// #[graphql_object(impl = [HumanValue, NodeValue])] +/// impl Luke { +/// fn id(&self) -> &ID { +/// &self.id +/// } +/// +/// // As `String` and `&str` aren't distinguished by +/// // GraphQL spec, you can use them interchangeably. +/// // Same is applied for `Cow<'a, str>`. +/// // ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ +/// fn home_planet() -> &'static str { +/// "Tatooine" +/// } +/// } +/// ``` +/// +/// # GraphQL subtyping and additional `null`able fields +/// +/// GraphQL allows implementers (both objects and other interfaces) to return +/// "subtypes" instead of an original value. Basically, this allows you to +/// impose additional bounds on the implementation. +/// +/// Valid "subtypes" are: +/// - interface implementer instead of an interface itself: +/// - `I implements T` in place of a `T`; +/// - `Vec` in place of a `Vec`. +/// - non-`null` value in place of a `null`able: +/// - `T` in place of a `Option`; +/// - `Vec` in place of a `Vec>`. +/// +/// These rules are recursively applied, so `Vec>` is a +/// valid "subtype" of a `Option>>>>`. +/// +/// Also, GraphQL allows implementers to add `null`able fields, which aren't +/// present on an original interface. +/// +/// ```rust +/// # extern crate juniper; +/// use juniper::{graphql_interface, graphql_object, ID}; +/// +/// #[graphql_interface(for = [HumanValue, Luke])] +/// struct Node { +/// id: ID, +/// } +/// +/// #[graphql_interface(for = HumanConnectionValue)] +/// struct Connection { +/// nodes: Vec, +/// } +/// +/// #[graphql_interface(impl = NodeValue, for = Luke)] +/// struct Human { +/// id: ID, +/// home_planet: String, +/// } +/// +/// #[graphql_interface(impl = ConnectionValue)] +/// struct HumanConnection { +/// nodes: Vec, +/// // ^^^^^^^^^^ notice not `NodeValue` +/// // This can happen, because every `Human` is a `Node` too, so we are +/// // just imposing additional bounds, which still can be resolved with +/// // `... on Connection { nodes }`. +/// } +/// +/// struct Luke { +/// id: ID, +/// } +/// +/// #[graphql_object(impl = [HumanValue, NodeValue])] +/// impl Luke { +/// fn id(&self) -> &ID { +/// &self.id +/// } +/// +/// fn home_planet(language: Option) -> &'static str { +/// // ^^^^^^^^^^^^^^ +/// // Notice additional `null`able field, which is missing on `Human`. +/// // Resolving `...on Human { homePlanet }` will provide `None` for +/// // this argument. +/// match language.as_deref() { +/// None | Some("en") => "Tatooine", +/// Some("ko") => "타투인", +/// _ => todo!(), +/// } +/// } +/// } +/// # +/// # fn main() {} +/// ``` +/// /// # Renaming policy /// /// By default, all [GraphQL interface][1] fields and their arguments are renamed @@ -797,7 +1128,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// // You can return `&str` even if trait definition returns `String`. /// fn detailed_info(&self, info_kind: String) -> &str { /// (info_kind == "planet") -/// .then(|| &self.home_planet) +/// .then_some(&self.home_planet) /// .unwrap_or(&self.id) /// } /// } @@ -953,8 +1284,8 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor /// [`ScalarValue`]: juniper::ScalarValue -/// [0]: https://spec.graphql.org/June2018 -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [0]: https://spec.graphql.org/October2021 +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html @@ -994,7 +1325,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// For more info and possibilities see [`#[graphql_interface]`] macro. /// /// [`#[graphql_interface]`]: crate::graphql_interface -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[proc_macro_error] #[proc_macro_derive(GraphQLInterface, attributes(graphql))] pub fn derive_interface(body: TokenStream) -> TokenStream { @@ -1131,7 +1462,7 @@ pub fn derive_interface(body: TokenStream) -> TokenStream { /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue -/// [1]: https://spec.graphql.org/June2018/#sec-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Objects #[proc_macro_error] #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(body: TokenStream) -> TokenStream { @@ -1455,8 +1786,8 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// [`GraphQLType`]: juniper::GraphQLType /// [`GraphQLValue`]: juniper::GraphQLValue /// [`ScalarValue`]: juniper::ScalarValue -/// [0]: https://spec.graphql.org/June2018 -/// [1]: https://spec.graphql.org/June2018/#sec-Objects +/// [0]: https://spec.graphql.org/October2021 +/// [1]: https://spec.graphql.org/October2021#sec-Objects #[proc_macro_error] #[proc_macro_attribute] pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { @@ -1509,7 +1840,7 @@ pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { /// [`GraphQLType`]: juniper::GraphQLType /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue /// [`Stream`]: futures::Stream -/// [1]: https://spec.graphql.org/June2018/#sec-Subscription +/// [1]: https://spec.graphql.org/October2021#sec-Subscription #[proc_macro_error] #[proc_macro_attribute] pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { @@ -1816,7 +2147,7 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// /// [`Context`]: juniper::Context /// [`ScalarValue`]: juniper::ScalarValue -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html #[proc_macro_error] #[proc_macro_derive(GraphQLUnion, attributes(graphql))] @@ -2106,7 +2437,7 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// /// [`Context`]: juniper::Context /// [`ScalarValue`]: juniper::ScalarValue -/// [1]: https://spec.graphql.org/June2018/#sec-Unions +/// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs deleted file mode 100644 index 8b58339a..00000000 --- a/juniper_codegen/src/result.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! - -use crate::util::duplicate::Duplicate; -use proc_macro2::Span; -use proc_macro_error::{Diagnostic, Level}; -use std::fmt; - -/// URL of the GraphQL specification (June 2018 Edition). -pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; - -pub enum GraphQLScope { - InterfaceAttr, - InterfaceDerive, - ObjectAttr, - ObjectDerive, - ScalarAttr, - ScalarDerive, - ScalarValueDerive, - UnionAttr, - UnionDerive, - DeriveInputObject, - DeriveEnum, -} - -impl GraphQLScope { - pub fn spec_section(&self) -> &str { - match self { - Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", - Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", - Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", - Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", - Self::UnionAttr | Self::UnionDerive => "#sec-Unions", - Self::DeriveInputObject => "#sec-Input-Objects", - Self::DeriveEnum => "#sec-Enums", - } - } -} - -impl fmt::Display for GraphQLScope { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = match self { - Self::InterfaceAttr | Self::InterfaceDerive => "interface", - Self::ObjectAttr | Self::ObjectDerive => "object", - Self::ScalarAttr | Self::ScalarDerive => "scalar", - Self::ScalarValueDerive => "built-in scalars", - Self::UnionAttr | Self::UnionDerive => "union", - Self::DeriveInputObject => "input object", - Self::DeriveEnum => "enum", - }; - write!(f, "GraphQL {}", name) - } -} - -#[allow(unused_variables)] -#[derive(Debug)] -pub enum UnsupportedAttribute { - Skip, - Interface, - Scalar, - Deprecation, - Default, -} - -impl GraphQLScope { - fn spec_link(&self) -> String { - format!("{}{}", SPEC_URL, self.spec_section()) - } - - pub fn custom>(&self, span: Span, msg: S) -> Diagnostic { - Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref())) - .note(self.spec_link()) - } - - pub fn error(&self, err: syn::Error) -> Diagnostic { - Diagnostic::spanned(err.span(), Level::Error, format!("{} {}", self, err)) - .note(self.spec_link()) - } - - pub fn emit_custom>(&self, span: Span, msg: S) { - self.custom(span, msg).emit() - } - - pub fn custom_error>(&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.spec_link()) - .emit(); - } - - pub fn duplicate<'a, T: syn::spanned::Spanned + 'a>( - &self, - duplicates: impl IntoIterator>, - ) { - 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.spec_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." - .into(), - ) - .note(format!("{}#sec-Schema", SPEC_URL)) - .emit(); - } -} diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index 023ba148..e0a0d78b 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -1,6 +1,6 @@ //! Code generation for `#[derive(ScalarValue)]` macro. -use std::{collections::HashMap, convert::TryFrom}; +use std::collections::HashMap; use proc_macro2::{Literal, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; @@ -12,14 +12,14 @@ use syn::{ visit::Visit, }; -use crate::{ - common::parse::{attr::err, ParseBufferExt as _}, - util::{filter_attrs, span_container::SpanContainer}, - GraphQLScope, +use crate::common::{ + diagnostic, filter_attrs, + parse::{attr::err, ParseBufferExt as _}, + SpanContainer, }; -/// [`GraphQLScope`] of errors for `#[derive(ScalarValue)]` macro. -const ERR: GraphQLScope = GraphQLScope::ScalarValueDerive; +/// [`diagnostic::Scope`] of errors for `#[derive(ScalarValue)]` macro. +const ERR: diagnostic::Scope = diagnostic::Scope::ScalarValueDerive; /// Expands `#[derive(ScalarValue)]` macro into generated code. pub fn expand_derive(input: TokenStream) -> syn::Result { @@ -55,11 +55,11 @@ pub fn expand_derive(input: TokenStream) -> syn::Result { (Method::AsBool, "as_bool"), ] .iter() - .filter_map(|(method, err)| (!methods.contains_key(method)).then(|| err)) + .filter_map(|(method, err)| (!methods.contains_key(method)).then_some(err)) .fold(None, |acc, &method| { Some( - acc.map(|acc| format!("{}, {}", acc, method)) - .unwrap_or_else(|| method.to_owned()), + acc.map(|acc| format!("{acc}, {method}")) + .unwrap_or_else(|| method.into()), ) }) .filter(|_| !attr.allow_missing_attrs); @@ -67,10 +67,9 @@ pub fn expand_derive(input: TokenStream) -> syn::Result { return Err(ERR.custom_error( span, format!( - "missing `#[value({})]` attributes. In case you are sure \ - that it's ok, use `#[value(allow_missing_attributes)]` to \ - suppress this error.", - missing_methods, + "missing `#[value({missing_methods})]` attributes. In case you \ + are sure that it's ok, use `#[value(allow_missing_attributes)]` \ + to suppress this error.", ), )); } @@ -298,10 +297,10 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gens ::juniper::ScalarValue for #ident#ty_gens + impl #impl_gens ::juniper::ScalarValue for #ident #ty_gens #where_clause { - #(#methods)* + #( #methods )* } } } @@ -333,20 +332,20 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gen ::std::convert::From<#var_ty> for #ty_ident#ty_gen + impl #impl_gen ::std::convert::From<#var_ty> for #ty_ident #ty_gen #where_clause { fn from(v: #var_ty) -> Self { - Self::#var_ident#var_field + Self::#var_ident #var_field } } #[automatically_derived] - impl#impl_gen ::std::convert::From<#ty_ident#ty_gen> for Option<#var_ty> + impl #impl_gen ::std::convert::From<#ty_ident #ty_gen> for Option<#var_ty> #where_clause { - fn from(ty: #ty_ident#ty_gen) -> Self { - if let #ty_ident::#var_ident#var_field = ty { + fn from(ty: #ty_ident #ty_gen) -> Self { + if let #ty_ident::#var_ident #var_field = ty { Some(v) } else { None @@ -355,12 +354,12 @@ impl Definition { } #[automatically_derived] - impl#lf_impl_gen ::std::convert::From<&'___a #ty_ident#ty_gen> for + impl #lf_impl_gen ::std::convert::From<&'___a #ty_ident #ty_gen> for Option<&'___a #var_ty> #where_clause { - fn from(ty: &'___a #ty_ident#ty_gen) -> Self { - if let #ty_ident::#var_ident#var_field = ty { + fn from(ty: &'___a #ty_ident #ty_gen) -> Self { + if let #ty_ident::#var_ident #var_field = ty { Some(v) } else { None @@ -404,17 +403,17 @@ impl Definition { .as_ref() .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); - quote! { Self::#var_ident#var_field => ::std::fmt::Display::fmt(v, f), } + quote! { Self::#var_ident #var_field => ::std::fmt::Display::fmt(v, f), } }); quote! { #[automatically_derived] - impl#impl_gen ::std::fmt::Display for #ident#ty_gen + impl #impl_gen ::std::fmt::Display for #ident #ty_gen #where_clause { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { match self { - #(#arms)* + #( #arms )* } } } @@ -440,7 +439,7 @@ impl Variant { fn match_arm(&self) -> TokenStream { let (ident, field) = (&self.ident, &self.field.match_arg()); quote! { - Self::#ident#field + Self::#ident #field } } } diff --git a/juniper_codegen/src/util/duplicate.rs b/juniper_codegen/src/util/duplicate.rs deleted file mode 100644 index b056eb71..00000000 --- a/juniper_codegen/src/util/duplicate.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! - -use std::collections::HashMap; - -pub struct Duplicate { - pub name: String, - pub spanned: Vec, -} - -impl Duplicate { - pub fn find_by_key<'a, F>(items: &'a [T], name: F) -> Option>> - 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::>(); - - if !duplicates.is_empty() { - Some(duplicates) - } else { - None - } - } -} diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs deleted file mode 100644 index 510c3aeb..00000000 --- a/juniper_codegen/src/util/mod.rs +++ /dev/null @@ -1,1333 +0,0 @@ -#![allow(clippy::single_match)] - -pub mod duplicate; -pub mod span_container; - -use std::{collections::HashMap, convert::TryFrom, str::FromStr}; - -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort; -use quote::{quote, quote_spanned}; -use span_container::SpanContainer; -use syn::{ - ext::IdentExt as _, - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, - spanned::Spanned, - token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, -}; - -use crate::common::parse::ParseBufferExt as _; - -/// Compares a path to a one-segment string value, -/// return true if equal. -pub fn path_eq_single(path: &syn::Path, value: &str) -> bool { - path.segments.len() == 1 && path.segments[0].ident == value -} - -#[derive(Debug)] -pub struct DeprecationAttr { - pub reason: Option, -} - -pub fn find_graphql_attr(attrs: &[Attribute]) -> Option<&Attribute> { - attrs - .iter() - .find(|attr| path_eq_single(&attr.path, "graphql")) -} - -/// Filters given `attrs` to contain attributes only with the given `name`. -pub fn filter_attrs<'a>( - name: &'a str, - attrs: &'a [Attribute], -) -> impl Iterator + 'a { - attrs - .iter() - .filter(move |attr| path_eq_single(&attr.path, name)) -} - -pub fn get_deprecated(attrs: &[Attribute]) -> Option> { - attrs - .iter() - .filter_map(|attr| match attr.parse_meta() { - Ok(Meta::List(ref list)) if list.path.is_ident("deprecated") => { - 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") => Some(SpanContainer::new( - path.span(), - None, - DeprecationAttr { reason: None }, - )), - _ => None, - }) - .next() -} - -fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr { - for meta in &list.nested { - if let NestedMeta::Meta(Meta::NameValue(ref nv)) = *meta { - if nv.path.is_ident("note") { - match nv.lit { - Lit::Str(ref strlit) => { - return DeprecationAttr { - reason: Some(strlit.value()), - }; - } - _ => abort!(syn::Error::new( - nv.lit.span(), - "only strings are allowed for deprecation", - )), - } - } else { - abort!(syn::Error::new( - nv.path.span(), - "unrecognized setting on #[deprecated(..)] attribute", - )); - } - } - } - DeprecationAttr { reason: None } -} - -// Gets doc comment. -pub fn get_doc_comment(attrs: &[Attribute]) -> Option> { - if let Some(items) = get_doc_attr(attrs) { - if let Some(doc_strings) = get_doc_strings(&items) { - return Some(doc_strings.map(|strings| join_doc_strings(&strings))); - } - } - None -} - -// Concatenates doc strings into one string. -fn join_doc_strings(docs: &[String]) -> String { - // Note: this is guaranteed since this function is only called - // from get_doc_strings(). - debug_assert!(!docs.is_empty()); - - let last_index = docs.len() - 1; - docs.iter() - .map(|s| s.as_str().trim_end()) - // Trim leading space. - .map(|s| s.strip_prefix(' ').unwrap_or(s)) - // Add newline, exept when string ends in a continuation backslash or is the last line. - .enumerate() - .fold(String::new(), |mut buffer, (index, s)| { - if index == last_index { - buffer.push_str(s); - } else if s.ends_with('\\') { - buffer.push_str(s.trim_end_matches('\\')); - buffer.push(' '); - } else { - buffer.push_str(s); - buffer.push('\n'); - } - buffer - }) -} - -// Gets doc strings from doc comment attributes. -fn get_doc_strings(items: &[MetaNameValue]) -> Option>> { - let mut span = None; - let comments = items - .iter() - .filter_map(|item| { - if item.path.is_ident("doc") { - match item.lit { - Lit::Str(ref strlit) => { - 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 { - None - } - }) - .collect::>(); - span.map(|span| SpanContainer::new(span, None, comments)) -} - -// Gets doc comment attributes. -fn get_doc_attr(attrs: &[Attribute]) -> Option> { - let mut docs = Vec::new(); - for attr in attrs { - match attr.parse_meta() { - Ok(Meta::NameValue(ref nv)) if nv.path.is_ident("doc") => docs.push(nv.clone()), - _ => {} - } - } - if !docs.is_empty() { - return Some(docs); - } - None -} - -// Note: duplicated from juniper crate! -#[doc(hidden)] -pub fn to_camel_case(s: &str) -> String { - let mut dest = String::new(); - - // Handle `_` and `__` to be more friendly with the `_var` convention for unused variables, and - // GraphQL introspection identifiers. - let s_iter = if let Some(s) = s.strip_prefix("__") { - dest.push_str("__"); - s - } else { - s.strip_prefix('_').unwrap_or(s) - } - .split('_') - .enumerate(); - - for (i, part) in s_iter { - if i > 0 && part.len() == 1 { - dest.push_str(&part.to_uppercase()); - } else if i > 0 && part.len() > 1 { - let first = part - .chars() - .next() - .unwrap() - .to_uppercase() - .collect::(); - let second = &part[1..]; - - dest.push_str(&first); - dest.push_str(second); - } else if i == 0 { - dest.push_str(part); - } - } - - dest -} - -pub(crate) fn to_upper_snake_case(s: &str) -> String { - let mut last_lower = false; - let mut upper = String::new(); - for c in s.chars() { - if c == '_' { - last_lower = false; - } else if c.is_lowercase() { - last_lower = true; - } else if c.is_uppercase() { - if last_lower { - upper.push('_'); - } - last_lower = false; - } - - for u in c.to_uppercase() { - upper.push(u); - } - } - upper -} - -#[doc(hidden)] -pub fn is_valid_name(field_name: &str) -> bool { - let mut chars = field_name.chars(); - - match chars.next() { - // first char can't be a digit - Some(c) if c.is_ascii_alphabetic() || c == '_' => (), - // can't be an empty string or any other character - _ => return false, - }; - - chars.all(|c| c.is_ascii_alphanumeric() || c == '_') -} - -/// The different possible ways to change case of fields in a struct, or variants in an enum. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum RenameRule { - /// Don't apply a default rename rule. - None, - /// Rename to "camelCase" style. - CamelCase, - /// Rename to "SCREAMING_SNAKE_CASE" style - ScreamingSnakeCase, -} - -impl RenameRule { - pub fn apply(&self, field: &str) -> String { - match self { - Self::None => field.to_owned(), - Self::CamelCase => to_camel_case(field), - Self::ScreamingSnakeCase => to_upper_snake_case(field), - } - } -} - -impl FromStr for RenameRule { - type Err = (); - - fn from_str(rule: &str) -> Result { - match rule { - "none" => Ok(Self::None), - "camelCase" => Ok(Self::CamelCase), - "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase), - _ => Err(()), - } - } -} - -impl TryFrom for RenameRule { - type Error = syn::Error; - - fn try_from(lit: syn::LitStr) -> syn::Result { - Self::from_str(&lit.value()).map_err(|_| syn::Error::new(lit.span(), "unknown rename rule")) - } -} - -impl Parse for RenameRule { - fn parse(input: ParseStream<'_>) -> syn::Result { - Self::try_from(input.parse::()?) - } -} - -#[derive(Default, Debug)] -pub struct ObjectAttributes { - pub name: Option>, - pub description: Option>, - pub context: Option>, - pub scalar: Option>, - pub interfaces: Vec>, - pub no_async: Option>, - pub is_internal: bool, - pub rename: Option, -} - -impl Parse for ObjectAttributes { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let val = input.parse::()?; - output.name = Some(SpanContainer::new( - ident.span(), - Some(val.span()), - val.value(), - )); - } - "description" => { - input.parse::()?; - let val = input.parse::()?; - output.description = Some(SpanContainer::new( - ident.span(), - Some(val.span()), - val.value(), - )); - } - "context" | "Context" => { - input.parse::()?; - // TODO: remove legacy support for string based Context. - let ctx = if let Ok(val) = input.parse::() { - eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated"); - eprintln!("Use a normal type instead - example: 'Context = MyContextType'"); - syn::parse_str::(&val.value())? - } else { - input.parse::()? - }; - output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)); - } - "scalar" | "Scalar" => { - input.parse::()?; - let val = input.parse::()?; - output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val)); - } - "impl" | "implements" | "interfaces" => { - input.parse::()?; - output.interfaces = input.parse_maybe_wrapped_and_punctuated::< - syn::Type, token::Bracket, token::Comma, - >()?.into_iter() - .map(|interface| { - SpanContainer::new(ident.span(), Some(interface.span()), interface) - }) - .collect(); - } - // FIXME: make this unneccessary. - "noasync" => { - output.no_async = Some(SpanContainer::new(ident.span(), None, ())); - } - "internal" => { - output.is_internal = true; - } - "rename" => { - input.parse::()?; - output.rename = Some(input.parse::()?); - } - _ => { - return Err(syn::Error::new(ident.span(), "unknown attribute")); - } - } - input.try_parse::()?; - } - - Ok(output) - } -} - -impl ObjectAttributes { - pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result { - let attr_opt = find_graphql_attr(attrs); - if let Some(attr) = attr_opt { - // Need to unwrap outer (), which are not present for proc macro attributes, - // but are present for regular ones. - - let mut a: Self = attr.parse_args()?; - if a.description.is_none() { - a.description = get_doc_comment(attrs); - } - Ok(a) - } else { - Ok(Self { - description: get_doc_comment(attrs), - ..Self::default() - }) - } - } -} - -#[derive(Debug)] -pub struct FieldAttributeArgument { - pub name: syn::Ident, - pub rename: Option>, - pub default: Option, - pub description: Option, -} - -impl Parse for FieldAttributeArgument { - fn parse(input: ParseStream<'_>) -> syn::Result { - let name = input.parse::()?.unraw(); - - let mut arg = Self { - name, - rename: None, - default: None, - description: None, - }; - - let content; - syn::parenthesized!(content in input); - while !content.is_empty() { - let name = content.parse::()?; - content.parse::()?; - - 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" => { - arg.description = Some(content.parse()?); - } - "default" => { - arg.default = Some(content.parse()?); - } - _ => return Err(syn::Error::new(name.span(), "unknown attribute")), - } - - // Discard trailing comma. - content.parse::().ok(); - } - - Ok(arg) - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub enum FieldAttributeParseMode { - Object, -} - -enum FieldAttribute { - Name(SpanContainer), - Description(SpanContainer), - Deprecation(SpanContainer), - Skip(SpanContainer), - Arguments(HashMap), - Default(Box>>), -} - -impl Parse for FieldAttribute { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = input.parse::()?; - - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let lit = input.parse::()?; - let raw = lit.value(); - if !is_valid_name(&raw) { - Err(syn::Error::new(lit.span(), "name consists of not allowed characters. (must match /^[_a-zA-Z][_a-zA-Z0-9]*$/)")) - } else { - Ok(FieldAttribute::Name(SpanContainer::new( - ident.span(), - Some(lit.span()), - lit, - ))) - } - } - "description" => { - input.parse::()?; - let lit = input.parse::()?; - Ok(FieldAttribute::Description(SpanContainer::new( - ident.span(), - Some(lit.span()), - lit, - ))) - } - "deprecated" | "deprecation" => { - let reason = if input.peek(token::Eq) { - input.parse::()?; - Some(input.parse::()?) - } else { - None - }; - 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(SpanContainer::new( - ident.span(), - None, - ident, - ))), - "arguments" => { - let arg_content; - syn::parenthesized!(arg_content in input); - let args = Punctuated::::parse_terminated( - &arg_content, - )?; - let map = args - .into_iter() - .map(|arg| (arg.name.to_string(), arg)) - .collect(); - Ok(FieldAttribute::Arguments(map)) - } - "default" => { - let default_expr = if input.peek(token::Eq) { - input.parse::()?; - let lit = input.parse::()?; - let default_expr = lit.parse::()?; - SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr)) - } else { - SpanContainer::new(ident.span(), None, None) - }; - - Ok(FieldAttribute::Default(Box::new(default_expr))) - } - _ => Err(syn::Error::new(ident.span(), "unknown attribute")), - } - } -} - -#[derive(Default)] -pub struct FieldAttributes { - pub name: Option>, - pub description: Option>, - pub deprecation: Option>, - /// Only relevant for GraphQLObject derive. - pub skip: Option>, - /// Only relevant for object macro. - pub arguments: HashMap, - /// Only relevant for object input objects. - pub default: Option>>, -} - -impl Parse for FieldAttributes { - fn parse(input: ParseStream<'_>) -> syn::Result { - let items = Punctuated::::parse_terminated(input)?; - - let mut output = Self::default(); - - for item in items { - match item { - FieldAttribute::Name(name) => { - output.name = Some(name.map(|val| val.value())); - } - FieldAttribute::Description(name) => { - output.description = Some(name.map(|val| val.value())); - } - FieldAttribute::Deprecation(attr) => { - output.deprecation = Some(attr); - } - FieldAttribute::Skip(ident) => { - output.skip = Some(ident); - } - FieldAttribute::Arguments(args) => { - output.arguments = args; - } - FieldAttribute::Default(expr) => { - output.default = Some(*expr); - } - } - } - - if !input.is_empty() { - Err(input.error("Unexpected input")) - } else { - Ok(output) - } - } -} - -impl FieldAttributes { - pub fn from_attrs( - attrs: &[syn::Attribute], - _mode: FieldAttributeParseMode, - ) -> syn::Result { - let doc_comment = get_doc_comment(attrs); - let deprecation = get_deprecated(attrs); - - let attr_opt = attrs.iter().find(|attr| attr.path.is_ident("graphql")); - - let mut output = match attr_opt { - Some(attr) => attr.parse_args()?, - None => Self::default(), - }; - - // Check for regular doc comment. - if output.description.is_none() { - output.description = doc_comment; - } - if output.deprecation.is_none() { - output.deprecation = deprecation; - } - - Ok(output) - } -} - -#[derive(Debug)] -pub struct GraphQLTypeDefinitionFieldArg { - pub name: String, - pub description: Option, - pub default: Option, - pub _type: Box, -} - -#[derive(Debug)] -pub struct GraphQLTypeDefinitionField { - pub name: String, - pub _type: syn::Type, - pub description: Option, - pub deprecation: Option, - pub args: Vec, - pub resolver_code: TokenStream, - pub is_type_inferred: bool, - pub is_async: bool, - pub default: Option, - pub span: Span, -} - -impl syn::spanned::Spanned for GraphQLTypeDefinitionField { - fn span(&self) -> Span { - 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 -/// by various macros. -/// The definition can be rendered to Rust code. -#[derive(Debug)] -pub struct GraphQLTypeDefiniton { - pub name: String, - pub _type: syn::Type, - pub context: Option, - pub scalar: Option, - pub description: Option, - pub fields: Vec, - pub generics: syn::Generics, - pub interfaces: Vec, - // Due to syn parsing differences, - // when parsing an impl the type generics are included in the type - // directly, but in syn::DeriveInput, the type generics are - // in the generics field. - // This flag signifies if the type generics need to be - // included manually. - pub include_type_generics: bool, - // This flag indicates if the generated code should always be - // generic over the ScalarValue. - // If false, the scalar is only generic if a generic parameter - // is specified manually. - pub generic_scalar: bool, - // FIXME: make this redundant. - pub no_async: bool, -} - -impl GraphQLTypeDefiniton { - #[allow(unused)] - fn has_async_field(&self) -> bool { - self.fields.iter().any(|field| field.is_async) - } - - pub fn into_enum_tokens(self) -> TokenStream { - 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::DefaultScalarValue) - } - }); - - let description = self - .description - .as_ref() - .map(|description| quote!( .description(#description) )); - - let values = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - - let descr = variant - .description - .as_ref() - .map(|description| quote!(Some(#description.to_string()))) - .unwrap_or_else(|| quote!(None)); - - let depr = variant - .deprecation - .as_ref() - .map(|deprecation| match deprecation.reason.as_ref() { - Some(reason) => quote!( ::juniper::meta::DeprecationStatus::Deprecated(Some(#reason.to_string())) ), - None => quote!( ::juniper::meta::DeprecationStatus::Deprecated(None) ), - }) - .unwrap_or_else(|| quote!(::juniper::meta::DeprecationStatus::Current)); - - quote!( - ::juniper::meta::EnumValue { - name: #variant_name.to_string(), - description: #descr, - deprecation_status: #depr, - }, - ) - }); - - let resolves = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - &#resolver_code => ::juniper::Value::scalar(String::from(#variant_name)), - ) - }); - - let from_inputs = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - Some(#variant_name) => Ok(#resolver_code), - ) - }); - - let to_inputs = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - &#resolver_code => - ::juniper::InputValue::scalar(#variant_name.to_string()), - ) - }); - - 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::ScalarValue)); - } - - 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: Sync)); - - let _async = quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty - #where_async - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [::juniper::Selection<#scalar>]>, - executor: &'a ::juniper::Executor, - ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - let mut body = quote!( - impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty - #where_clause { } - - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty - #where_clause { } - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty - #where_clause - { - fn name(_: &()) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - _: &(), - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - registry.build_enum_type::<#ty>(&(), &[ - #( #values )* - ]) - #description - .into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve( - &self, - _: &(), - _: Option<&[::juniper::Selection<#scalar>]>, - _: &::juniper::Executor - ) -> ::juniper::ExecutionResult<#scalar> { - let v = match self { - #( #resolves )* - }; - Ok(v) - } - } - - impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty - #where_clause - { - type Error = ::std::string::String; - - fn from_input_value( - v: &::juniper::InputValue<#scalar> - ) -> Result<#ty, Self::Error> { - match v.as_enum_value().or_else(|| v.as_string_value()) { - #( #from_inputs )* - _ => Err(format!("Unknown enum value: {}", v)), - } - } - } - - impl#impl_generics ::juniper::ToInputValue<#scalar> for #ty - #where_clause - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - match self { - #( #to_inputs )* - } - } - } - - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty - #where_clause - { - const NAME: ::juniper::macros::reflect::Type = #name; - } - - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty - #where_clause - { - const NAMES: ::juniper::macros::reflect::Types = - &[>::NAME]; - } - - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty - #where_clause - { - const VALUE: ::juniper::macros::reflect::WrappedValue = 1; - } - ); - - if !self.no_async { - body.extend(_async) - } - - body - } - - pub fn into_input_object_tokens(self) -> TokenStream { - 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::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::>(); - - 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::InputValue::Null) | None if true => #def, - } - } - None => quote! {}, - }; - - quote!( - #field_ident: { - match obj.get(#field_name) { - #from_input_default - Some(ref v) => { - ::juniper::FromInputValue::<#scalar>::from_input_value(v) - .map_err(::juniper::IntoFieldError::into_field_error)? - }, - None => { - ::juniper::FromInputValue::<#scalar>::from_implicit_null() - .map_err(::juniper::IntoFieldError::into_field_error)? - }, - } - }, - ) - }) - .collect::>(); - - 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::>(); - - 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::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: Sync)); - - let async_type = quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens - #where_async - {} - ); - - let marks = self.fields.iter().map(|field| { - let field_ty = &field._type; - quote_spanned! { field_ty.span() => - <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); - } - }); - - let mut body = quote!( - impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty #type_generics_tokens - #where_clause { - fn mark() { - #( #marks )* - } - } - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn name(_: &()) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - _: &(), - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::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::GraphQLValue<#scalar> for #ty #type_generics_tokens - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - } - - impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty #type_generics_tokens - #where_clause - { - type Error = ::juniper::FieldError<#scalar>; - - fn from_input_value( - value: &::juniper::InputValue<#scalar> - ) -> Result { - let obj = value - .to_object_value() - .ok_or_else(|| ::juniper::FieldError::<#scalar>::from( - format!("Expected input object, found: {}", value)) - )?; - Ok(#ty { - #( #from_inputs )* - }) - } - } - - impl#impl_generics ::juniper::ToInputValue<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - ::juniper::InputValue::object(vec![ - #( #to_inputs )* - ].into_iter().collect()) - } - } - - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> - for #ty #type_generics_tokens - #where_clause - { - const NAME: ::juniper::macros::reflect::Type = #name; - } - - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> - for #ty #type_generics_tokens - #where_clause - { - const NAMES: ::juniper::macros::reflect::Types = - &[>::NAME]; - } - - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> - for #ty #type_generics_tokens - #where_clause - { - const VALUE: ::juniper::macros::reflect::WrappedValue = 1; - } - ); - - if !self.no_async { - body.extend(async_type); - } - - body - } -} - -#[cfg(test)] -mod test { - use super::*; - use syn::{Ident, LitStr}; - - fn strs_to_strings(source: Vec<&str>) -> Vec { - source - .iter() - .map(|x| (*x).to_string()) - .collect::>() - } - - fn litstr(s: &str) -> Lit { - Lit::Str(LitStr::new(s, Span::call_site())) - } - - fn ident(s: &str) -> Ident { - quote::format_ident!("{}", s) - } - - mod test_get_doc_strings { - use super::*; - - #[test] - fn test_single() { - let result = get_doc_strings(&[MetaNameValue { - path: ident("doc").into(), - eq_token: Default::default(), - lit: litstr("foo"), - }]); - assert_eq!( - &result.unwrap(), - Some(&strs_to_strings(vec!["foo"])).unwrap() - ); - } - - #[test] - fn test_many() { - let result = get_doc_strings(&[ - MetaNameValue { - path: ident("doc").into(), - eq_token: Default::default(), - lit: litstr("foo"), - }, - MetaNameValue { - path: ident("doc").into(), - eq_token: Default::default(), - lit: litstr("\n"), - }, - MetaNameValue { - path: ident("doc").into(), - eq_token: Default::default(), - lit: litstr("bar"), - }, - ]); - assert_eq!( - &result.unwrap(), - Some(&strs_to_strings(vec!["foo", "\n", "bar"])).unwrap() - ); - } - - #[test] - fn test_not_doc() { - let result = get_doc_strings(&[MetaNameValue { - path: ident("blah").into(), - eq_token: Default::default(), - lit: litstr("foo"), - }]); - assert_eq!(&result, &None); - } - } - - mod test_join_doc_strings { - use super::*; - - #[test] - fn test_single() { - let result = join_doc_strings(&strs_to_strings(vec!["foo"])); - assert_eq!(&result, "foo"); - } - #[test] - fn test_multiple() { - let result = join_doc_strings(&strs_to_strings(vec!["foo", "bar"])); - assert_eq!(&result, "foo\nbar"); - } - - #[test] - fn test_trims_spaces() { - let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"])); - assert_eq!(&result, "foo\nbar\nbaz"); - } - - #[test] - fn test_empty() { - let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"])); - assert_eq!(&result, "foo\n\nbar"); - } - - #[test] - fn test_newline_spaces() { - let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"])); - assert_eq!(&result, "foo\n\nbar"); - } - - #[test] - fn test_continuation_backslash() { - let result = join_doc_strings(&strs_to_strings(vec!["foo\\", "x\\", "y", "bar"])); - assert_eq!(&result, "foo x y\nbar"); - } - } - - #[test] - fn test_to_camel_case() { - assert_eq!(&to_camel_case("test")[..], "test"); - assert_eq!(&to_camel_case("_test")[..], "test"); - assert_eq!(&to_camel_case("__test")[..], "__test"); - assert_eq!(&to_camel_case("first_second")[..], "firstSecond"); - assert_eq!(&to_camel_case("first_")[..], "first"); - assert_eq!(&to_camel_case("a_b_c")[..], "aBC"); - assert_eq!(&to_camel_case("a_bc")[..], "aBc"); - assert_eq!(&to_camel_case("a_b")[..], "aB"); - assert_eq!(&to_camel_case("a")[..], "a"); - assert_eq!(&to_camel_case("")[..], ""); - } - - #[test] - fn test_to_upper_snake_case() { - assert_eq!(to_upper_snake_case("abc"), "ABC"); - assert_eq!(to_upper_snake_case("a_bc"), "A_BC"); - assert_eq!(to_upper_snake_case("ABC"), "ABC"); - assert_eq!(to_upper_snake_case("A_BC"), "A_BC"); - assert_eq!(to_upper_snake_case("SomeInput"), "SOME_INPUT"); - assert_eq!(to_upper_snake_case("someInput"), "SOME_INPUT"); - assert_eq!(to_upper_snake_case("someINpuT"), "SOME_INPU_T"); - assert_eq!(to_upper_snake_case("some_INpuT"), "SOME_INPU_T"); - } - - #[test] - fn test_is_valid_name() { - assert_eq!(is_valid_name("yesItIs"), true); - assert_eq!(is_valid_name("NoitIsnt"), true); - assert_eq!(is_valid_name("iso6301"), true); - assert_eq!(is_valid_name("thisIsATest"), true); - assert_eq!(is_valid_name("i6Op"), true); - assert_eq!(is_valid_name("i!"), false); - assert_eq!(is_valid_name(""), false); - assert_eq!(is_valid_name("aTest"), true); - assert_eq!(is_valid_name("__Atest90"), true); - } -} diff --git a/juniper_graphql_ws/Cargo.toml b/juniper_graphql_ws/Cargo.toml index 3b74f4bb..c8b68806 100644 --- a/juniper_graphql_ws/Cargo.toml +++ b/juniper_graphql_ws/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_graphql_ws" version = "0.4.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "GraphQL over WebSocket Protocol implementation for `juniper` crate." license = "BSD-2-Clause" authors = ["Christopher Brown "] diff --git a/juniper_graphql_ws/src/client_message.rs b/juniper_graphql_ws/src/client_message.rs index c893f6da..767f6969 100644 --- a/juniper_graphql_ws/src/client_message.rs +++ b/juniper_graphql_ws/src/client_message.rs @@ -78,11 +78,11 @@ mod test { assert_eq!( ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "query MyQuery { __typename }".to_string(), + query: "query MyQuery { __typename }".into(), variables: graphql_vars! {"foo": "bar"}, - operation_name: Some("MyQuery".to_string()), + operation_name: Some("MyQuery".into()), }, }, serde_json::from_str( @@ -99,9 +99,9 @@ mod test { assert_eq!( ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "query MyQuery { __typename }".to_string(), + query: "query MyQuery { __typename }".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -115,9 +115,7 @@ mod test { ); assert_eq!( - ClientMessage::Stop { - id: "foo".to_string() - }, + ClientMessage::Stop { id: "foo".into() }, serde_json::from_str(r##"{"type": "stop", "id": "foo"}"##).unwrap(), ); diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index cfb8b967..43f01f55 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -1,6 +1,5 @@ #![doc = include_str!("../README.md")] -#![deny(missing_docs)] -#![deny(warnings)] +#![deny(missing_docs, warnings)] mod client_message; pub use client_message::*; @@ -14,13 +13,8 @@ pub use schema::*; mod utils; use std::{ - collections::HashMap, - convert::{Infallible, TryInto}, - error::Error, - marker::PhantomPinned, - pin::Pin, - sync::Arc, - time::Duration, + collections::HashMap, convert::Infallible, error::Error, marker::PhantomPinned, pin::Pin, + sync::Arc, time::Duration, }; use juniper::{ @@ -292,9 +286,10 @@ impl> ConnectionState { } async fn start(id: String, params: ExecutionParams) -> BoxStream<'static, Reaction> { - // TODO: This could be made more efficient if juniper exposed functionality to allow us to - // parse and validate the query, determine whether it's a subscription, and then execute - // it. For now, the query gets parsed and validated twice. + // TODO: This could be made more efficient if `juniper` exposed + // functionality to allow us to parse and validate the query, + // determine whether it's a subscription, and then execute it. + // For now, the query gets parsed and validated twice. let params = Arc::new(params); @@ -319,8 +314,7 @@ impl> ConnectionState { Err(e) => { return Reaction::ServerMessage(ServerMessage::Error { id: id.clone(), - // e only references data owned by params. The new ErrorPayload will continue to keep that data alive. - payload: unsafe { ErrorPayload::new_unchecked(Box::new(params.clone()), e) }, + payload: ErrorPayload::new(Box::new(params.clone()), e), }) .into_stream(); } @@ -358,10 +352,7 @@ enum SubscriptionStartState { id: String, future: BoxFuture< 'static, - Result< - juniper_subscriptions::Connection<'static, S::ScalarValue>, - GraphQLError<'static>, - >, + Result, GraphQLError>, >, }, /// Streaming is the state after we've successfully obtained the event stream for the @@ -441,10 +432,7 @@ impl Stream for SubscriptionStart { return Poll::Ready(Some(Reaction::ServerMessage( ServerMessage::Error { id: id.clone(), - // e only references data owned by params. The new ErrorPayload will continue to keep that data alive. - payload: unsafe { - ErrorPayload::new_unchecked(Box::new(params.clone()), e) - }, + payload: ErrorPayload::new(Box::new(params.clone()), e), }, ))); } @@ -629,7 +617,7 @@ mod test { use juniper::{ futures::sink::SinkExt, graphql_input_value, graphql_object, graphql_subscription, graphql_value, graphql_vars, - parser::{ParseError, Spanning, Token}, + parser::{ParseError, Spanning}, DefaultScalarValue, EmptyMutation, FieldError, FieldResult, RootNode, }; @@ -710,9 +698,9 @@ mod test { assert_eq!(ServerMessage::ConnectionAck, conn.next().await.unwrap()); conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "{context}".to_string(), + query: "{context}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -722,7 +710,7 @@ mod test { assert_eq!( ServerMessage::Data { - id: "foo".to_string(), + id: "foo".into(), payload: DataPayload { data: graphql_value!({"context": 1}), errors: vec![], @@ -732,9 +720,7 @@ mod test { ); assert_eq!( - ServerMessage::Complete { - id: "foo".to_string(), - }, + ServerMessage::Complete { id: "foo".into() }, conn.next().await.unwrap() ); } @@ -755,9 +741,9 @@ mod test { assert_eq!(ServerMessage::ConnectionAck, conn.next().await.unwrap()); conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "subscription Foo {context}".to_string(), + query: "subscription Foo {context}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -767,7 +753,7 @@ mod test { assert_eq!( ServerMessage::Data { - id: "foo".to_string(), + id: "foo".into(), payload: DataPayload { data: graphql_value!({"context": 1}), errors: vec![], @@ -777,9 +763,9 @@ mod test { ); conn.send(ClientMessage::Start { - id: "bar".to_string(), + id: "bar".into(), payload: StartPayload { - query: "subscription Bar {context}".to_string(), + query: "subscription Bar {context}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -789,7 +775,7 @@ mod test { assert_eq!( ServerMessage::Data { - id: "bar".to_string(), + id: "bar".into(), payload: DataPayload { data: graphql_value!({"context": 1}), errors: vec![], @@ -798,16 +784,12 @@ mod test { conn.next().await.unwrap() ); - conn.send(ClientMessage::Stop { - id: "foo".to_string(), - }) - .await - .unwrap(); + conn.send(ClientMessage::Stop { id: "foo".into() }) + .await + .unwrap(); assert_eq!( - ServerMessage::Complete { - id: "foo".to_string(), - }, + ServerMessage::Complete { id: "foo".into() }, conn.next().await.unwrap() ); } @@ -844,7 +826,7 @@ mod test { assert_eq!( ServerMessage::ConnectionError { payload: ConnectionErrorPayload { - message: "init error".to_string(), + message: "init error".into(), }, }, conn.next().await.unwrap() @@ -869,9 +851,9 @@ mod test { assert_eq!(ServerMessage::ConnectionAck, conn.next().await.unwrap()); conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "subscription Foo {never}".to_string(), + query: "subscription Foo {never}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -880,9 +862,9 @@ mod test { .unwrap(); conn.send(ClientMessage::Start { - id: "bar".to_string(), + id: "bar".into(), payload: StartPayload { - query: "subscription Bar {never}".to_string(), + query: "subscription Bar {never}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -894,7 +876,7 @@ mod test { ServerMessage::Error { id, .. } => { assert_eq!(id, "bar"); } - msg @ _ => panic!("expected error, got: {:?}", msg), + msg @ _ => panic!("expected error, got: {msg:?}"), } } @@ -914,9 +896,9 @@ mod test { assert_eq!(ServerMessage::ConnectionAck, conn.next().await.unwrap()); conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "asd".to_string(), + query: "asd".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -929,13 +911,13 @@ mod test { assert_eq!(id, "foo"); match payload.graphql_error() { GraphQLError::ParseError(Spanning { - item: ParseError::UnexpectedToken(Token::Name("asd")), + item: ParseError::UnexpectedToken(token), .. - }) => {} - p @ _ => panic!("expected graphql parse error, got: {:?}", p), + }) => assert_eq!(token, "asd"), + p @ _ => panic!("expected graphql parse error, got: {p:?}"), } } - msg @ _ => panic!("expected error, got: {:?}", msg), + msg @ _ => panic!("expected error, got: {msg:?}"), } } @@ -977,9 +959,9 @@ mod test { // If we send the start message before the init is handled, we should still get results. conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "{context}".to_string(), + query: "{context}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -991,7 +973,7 @@ mod test { assert_eq!( ServerMessage::Data { - id: "foo".to_string(), + id: "foo".into(), payload: DataPayload { data: graphql_value!({"context": 1}), errors: vec![], @@ -1017,9 +999,9 @@ mod test { assert_eq!(ServerMessage::ConnectionAck, conn.next().await.unwrap()); conn.send(ClientMessage::Start { - id: "foo".to_string(), + id: "foo".into(), payload: StartPayload { - query: "subscription Foo {error}".to_string(), + query: "subscription Foo {error}".into(), variables: graphql_vars! {}, operation_name: None, }, @@ -1036,7 +1018,7 @@ mod test { assert_eq!(data, graphql_value!({ "error": null })); assert_eq!(errors.len(), 1); } - msg @ _ => panic!("expected data, got: {:?}", msg), + msg @ _ => panic!("expected data, got: {msg:?}"), } } } diff --git a/juniper_graphql_ws/src/server_message.rs b/juniper_graphql_ws/src/server_message.rs index f52c1244..6e2fce9a 100644 --- a/juniper_graphql_ws/src/server_message.rs +++ b/juniper_graphql_ws/src/server_message.rs @@ -1,10 +1,10 @@ -use std::{any::Any, fmt, marker::PhantomPinned, mem}; +use std::{any::Any, fmt, marker::PhantomPinned}; use juniper::{ExecutionError, GraphQLError, Value}; use serde::{Serialize, Serializer}; /// The payload for errors that are not associated with a GraphQL operation. -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectionErrorPayload { /// The error message. @@ -32,26 +32,23 @@ pub struct DataPayload { // _execution_params). pub struct ErrorPayload { _execution_params: Option>, - error: GraphQLError<'static>, + error: GraphQLError, _marker: PhantomPinned, } impl ErrorPayload { - /// For this to be okay, the caller must guarantee that the error can only reference data from - /// execution_params and that execution_params has not been modified or moved. - pub(crate) unsafe fn new_unchecked( - execution_params: Box, - error: GraphQLError<'_>, - ) -> Self { + /// Creates a new [`ErrorPayload`] out of the provide `execution_params` and + /// [`GraphQLError`]. + pub(crate) fn new(execution_params: Box, error: GraphQLError) -> Self { Self { _execution_params: Some(execution_params), - error: mem::transmute(error), + error, _marker: PhantomPinned, } } /// Returns the contained GraphQLError. - pub fn graphql_error<'a>(&'a self) -> &GraphQLError<'a> { + pub fn graphql_error(&self) -> &GraphQLError { &self.error } } @@ -77,8 +74,8 @@ impl Serialize for ErrorPayload { } } -impl From> for ErrorPayload { - fn from(error: GraphQLError<'static>) -> Self { +impl From for ErrorPayload { + fn from(error: GraphQLError) -> Self { Self { _execution_params: None, error, @@ -143,7 +140,7 @@ mod test { assert_eq!( serde_json::to_string(&ServerMessage::ConnectionError { payload: ConnectionErrorPayload { - message: "foo".to_string(), + message: "foo".into(), }, }) .unwrap(), @@ -157,7 +154,7 @@ mod test { assert_eq!( serde_json::to_string(&ServerMessage::Data { - id: "foo".to_string(), + id: "foo".into(), payload: DataPayload { data: graphql_value!(null), errors: vec![], @@ -169,7 +166,7 @@ mod test { assert_eq!( serde_json::to_string(&ServerMessage::Error { - id: "foo".to_string(), + id: "foo".into(), payload: GraphQLError::UnknownOperationName.into(), }) .unwrap(), @@ -177,10 +174,7 @@ mod test { ); assert_eq!( - serde_json::to_string(&ServerMessage::Complete { - id: "foo".to_string(), - }) - .unwrap(), + serde_json::to_string(&ServerMessage::Complete { id: "foo".into() }).unwrap(), r##"{"type":"complete","id":"foo"}"##, ); diff --git a/juniper_hyper/CHANGELOG.md b/juniper_hyper/CHANGELOG.md index e0e722ea..435c134e 100644 --- a/juniper_hyper/CHANGELOG.md +++ b/juniper_hyper/CHANGELOG.md @@ -11,6 +11,10 @@ All user visible changes to `juniper_hyper` crate will be documented in this fil ### BC Breaks - Switched to 0.16 version of [`juniper` crate]. +- Changed return type of all functions from `Response` to `Response`. ([#1101], [#1096]) + +[#1096]: /../../issues/1096 +[#1101]: /../../pull/1101 diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index 72ab1f4f..59f1c8a1 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_hyper" version = "0.9.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "`juniper` GraphQL integration with `hyper`." license = "BSD-2-Clause" authors = ["Damir Vandic "] @@ -14,7 +15,7 @@ keywords = ["apollo", "graphql", "hyper", "juniper"] exclude = ["/examples/", "/release.toml"] [dependencies] -futures = "0.3.1" +futures = "0.3.22" hyper = { version = "0.14", features = ["server", "runtime"] } juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } serde_json = "1.0" diff --git a/juniper_hyper/examples/hyper_server.rs b/juniper_hyper/examples/hyper_server.rs index 3187451e..069e0c6f 100644 --- a/juniper_hyper/examples/hyper_server.rs +++ b/juniper_hyper/examples/hyper_server.rs @@ -3,7 +3,7 @@ use std::{convert::Infallible, sync::Arc}; use hyper::{ server::Server, service::{make_service_fn, service_fn}, - Body, Method, Response, StatusCode, + Method, Response, StatusCode, }; use juniper::{ tests::fixtures::starwars::schema::{Database, Query}, @@ -38,7 +38,7 @@ async fn main() { juniper_hyper::graphql(root_node, ctx, req).await } _ => { - let mut response = Response::new(Body::empty()); + let mut response = Response::new(String::new()); *response.status_mut() = StatusCode::NOT_FOUND; response } @@ -49,9 +49,9 @@ async fn main() { }); let server = Server::bind(&addr).serve(new_service); - println!("Listening on http://{}", addr); + println!("Listening on http://{addr}"); if let Err(e) = server.await { - eprintln!("server error: {}", e) + eprintln!("server error: {e}") } } diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index ce9204eb..4836a66c 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -17,7 +17,7 @@ pub async fn graphql_sync( root_node: Arc>, context: Arc, req: Request, -) -> Response +) -> Response where QueryT: GraphQLType, QueryT::TypeInfo: Sync, @@ -38,7 +38,7 @@ pub async fn graphql( root_node: Arc>, context: Arc, req: Request, -) -> Response +) -> Response where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -57,7 +57,7 @@ where async fn parse_req( req: Request, -) -> Result, Response> { +) -> Result, Response> { match *req.method() { Method::GET => parse_get_req(req), Method::POST => { @@ -84,7 +84,7 @@ fn parse_get_req( .map(|q| gql_request_from_get(q).map(GraphQLBatchRequest::Single)) .unwrap_or_else(|| { Err(GraphQLRequestError::Invalid( - "'query' parameter is missing".to_string(), + "'query' parameter is missing".into(), )) }) } @@ -121,32 +121,27 @@ async fn parse_post_graphql_req( pub async fn graphiql( graphql_endpoint: &str, subscriptions_endpoint: Option<&str>, -) -> Response { +) -> Response { let mut resp = new_html_response(StatusCode::OK); // XXX: is the call to graphiql_source blocking? - *resp.body_mut() = Body::from(juniper::http::graphiql::graphiql_source( - graphql_endpoint, - subscriptions_endpoint, - )); + *resp.body_mut() = + juniper::http::graphiql::graphiql_source(graphql_endpoint, subscriptions_endpoint); resp } pub async fn playground( graphql_endpoint: &str, subscriptions_endpoint: Option<&str>, -) -> Response { +) -> Response { let mut resp = new_html_response(StatusCode::OK); - *resp.body_mut() = Body::from(juniper::http::playground::playground_source( - graphql_endpoint, - subscriptions_endpoint, - )); + *resp.body_mut() = + juniper::http::playground::playground_source(graphql_endpoint, subscriptions_endpoint); resp } -fn render_error(err: GraphQLRequestError) -> Response { - let message = format!("{}", err); +fn render_error(err: GraphQLRequestError) -> Response { let mut resp = new_response(StatusCode::BAD_REQUEST); - *resp.body_mut() = Body::from(message); + *resp.body_mut() = err.to_string(); resp } @@ -154,7 +149,7 @@ async fn execute_request_sync( root_node: Arc>, context: Arc, request: GraphQLBatchRequest, -) -> Response +) -> Response where QueryT: GraphQLType, QueryT::TypeInfo: Sync, @@ -166,7 +161,7 @@ where S: ScalarValue + Send + Sync, { let res = request.execute_sync(&*root_node, &context); - let body = Body::from(serde_json::to_string_pretty(&res).unwrap()); + let body = serde_json::to_string_pretty(&res).unwrap(); let code = if res.is_ok() { StatusCode::OK } else { @@ -185,7 +180,7 @@ async fn execute_request( root_node: Arc>, context: Arc, request: GraphQLBatchRequest, -) -> Response +) -> Response where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, @@ -197,7 +192,7 @@ where S: ScalarValue + Send + Sync, { let res = request.execute(&*root_node, &context).await; - let body = Body::from(serde_json::to_string_pretty(&res).unwrap()); + let body = serde_json::to_string_pretty(&res).unwrap(); let code = if res.is_ok() { StatusCode::OK } else { @@ -249,25 +244,24 @@ where match query { Some(query) => Ok(JuniperGraphQLRequest::new(query, operation_name, variables)), None => Err(GraphQLRequestError::Invalid( - "'query' parameter is missing".to_string(), + "'query' parameter is missing".into(), )), } } fn invalid_err(parameter_name: &str) -> GraphQLRequestError { GraphQLRequestError::Invalid(format!( - "'{}' parameter is specified multiple times", - parameter_name + "`{parameter_name}` parameter is specified multiple times", )) } -fn new_response(code: StatusCode) -> Response { - let mut r = Response::new(Body::empty()); +fn new_response(code: StatusCode) -> Response { + let mut r = Response::new(String::new()); *r.status_mut() = code; r } -fn new_html_response(code: StatusCode) -> Response { +fn new_html_response(code: StatusCode) -> Response { let mut resp = new_response(code); resp.headers_mut().insert( header::CONTENT_TYPE, @@ -314,7 +308,7 @@ mod tests { use hyper::{ server::Server, service::{make_service_fn, service_fn}, - Body, Method, Response, StatusCode, + Method, Response, StatusCode, }; use juniper::{ http::tests as http_tests, @@ -330,31 +324,31 @@ mod tests { impl http_tests::HttpIntegration for TestHyperIntegration { fn get(&self, url: &str) -> http_tests::TestResponse { - let url = format!("http://127.0.0.1:{}/graphql{}", self.port, url); - make_test_response(reqwest::blocking::get(&url).expect(&format!("failed GET {}", url))) + let url = format!("http://127.0.0.1:{}/graphql{url}", self.port); + make_test_response(reqwest::blocking::get(&url).expect(&format!("failed GET {url}"))) } fn post_json(&self, url: &str, body: &str) -> http_tests::TestResponse { - let url = format!("http://127.0.0.1:{}/graphql{}", self.port, url); + let url = format!("http://127.0.0.1:{}/graphql{url}", self.port); let client = reqwest::blocking::Client::new(); let res = client .post(&url) .header(reqwest::header::CONTENT_TYPE, "application/json") - .body(body.to_string()) + .body(body.to_owned()) .send() - .expect(&format!("failed POST {}", url)); + .expect(&format!("failed POST {url}")); make_test_response(res) } fn post_graphql(&self, url: &str, body: &str) -> http_tests::TestResponse { - let url = format!("http://127.0.0.1:{}/graphql{}", self.port, url); + let url = format!("http://127.0.0.1:{}/graphql{url}", self.port); let client = reqwest::blocking::Client::new(); let res = client .post(&url) .header(reqwest::header::CONTENT_TYPE, "application/graphql") - .body(body.to_string()) + .body(body.to_owned()) .send() - .expect(&format!("failed POST {}", url)); + .expect(&format!("failed POST {url}")); make_test_response(res) } } @@ -362,11 +356,9 @@ mod tests { fn make_test_response(response: ReqwestResponse) -> http_tests::TestResponse { let status_code = response.status().as_u16() as i32; let content_type_header = response.headers().get(reqwest::header::CONTENT_TYPE); - let content_type = if let Some(ct) = content_type_header { - format!("{}", ct.to_str().unwrap()) - } else { - String::default() - }; + let content_type = content_type_header + .map(|ct| ct.to_str().unwrap().into()) + .unwrap_or_default(); let body = response.text().unwrap(); http_tests::TestResponse { @@ -412,7 +404,7 @@ mod tests { super::graphql(root_node, ctx, req).await } } else { - let mut resp = Response::new(Body::empty()); + let mut resp = Response::new(String::new()); *resp.status_mut() = StatusCode::NOT_FOUND; resp }) @@ -439,7 +431,7 @@ mod tests { }); if let Err(e) = server.await { - eprintln!("server error: {}", e); + eprintln!("server error: {e}"); } } diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml index d4b4b759..7eef05d3 100644 --- a/juniper_iron/Cargo.toml +++ b/juniper_iron/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_iron" version = "0.8.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "`juniper` GraphQL integration with `iron`." license = "BSD-2-Clause" authors = [ @@ -17,7 +18,7 @@ keywords = ["apollo", "graphql", "iron", "juniper"] exclude = ["/examples/", "/release.toml"] [dependencies] -futures = "0.3.1" +futures = "0.3.22" iron = ">= 0.5, < 0.7" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } serde_json = "1.0.2" diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs index d243b221..218af60b 100644 --- a/juniper_iron/examples/iron_server.rs +++ b/juniper_iron/examples/iron_server.rs @@ -33,7 +33,7 @@ fn main() { chain.link_before(logger_before); chain.link_after(logger_after); - let host = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8080".to_owned()); - println!("GraphQL server started on {}", host); + let host = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8080".into()); + println!("GraphQL server started on {host}"); Iron::new(chain).http(host.as_str()).unwrap(); } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 6e104ea0..b8829614 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -175,8 +175,8 @@ impl GraphiQLHandler { /// relative, so a common value could be `"/graphql"`. pub fn new(graphql_url: &str, subscription_url: Option<&str>) -> GraphiQLHandler { GraphiQLHandler { - graphql_url: graphql_url.to_owned(), - subscription_url: subscription_url.map(|s| s.to_owned()), + graphql_url: graphql_url.into(), + subscription_url: subscription_url.map(Into::into), } } } @@ -188,14 +188,14 @@ impl PlaygroundHandler { /// relative, so a common value could be `"/graphql"`. pub fn new(graphql_url: &str, subscription_url: Option<&str>) -> PlaygroundHandler { PlaygroundHandler { - graphql_url: graphql_url.to_owned(), - subscription_url: subscription_url.map(|s| s.to_owned()), + graphql_url: graphql_url.into(), + subscription_url: subscription_url.map(Into::into), } } } -impl<'a, CtxFactory, Query, Mutation, Subscription, CtxT, S> Handler - for GraphQLHandler<'a, CtxFactory, Query, Mutation, Subscription, CtxT, S> +impl Handler + for GraphQLHandler<'static, CtxFactory, Query, Mutation, Subscription, CtxT, S> where S: ScalarValue + Sync + Send + 'static, CtxFactory: Fn(&mut Request) -> IronResult + Send + Sync + 'static, @@ -203,7 +203,6 @@ where Query: GraphQLType + Send + Sync + 'static, Mutation: GraphQLType + Send + Sync + 'static, Subscription: GraphQLType + Send + Sync + 'static, - 'a: 'static, { fn handle(&self, req: &mut Request) -> IronResult { let context = (self.context_factory)(req)?; @@ -284,7 +283,7 @@ impl Error for GraphQLIronError { impl From for IronError { fn from(err: GraphQLIronError) -> IronError { - let message = format!("{}", err); + let message = err.to_string(); IronError::new(err, (status::BadRequest, message)) } } @@ -314,17 +313,16 @@ mod tests { // This is ugly but it works. `iron_test` just dumps the path/url in headers // and newer `hyper` doesn't allow unescaped "{" or "}". fn fixup_url(url: &str) -> String { - let url = Url::parse(&format!("http://localhost:3000{}", url)).expect("url to parse"); + let url = Url::parse(&format!("http://localhost:3000{url}")).expect("url to parse"); let path: String = url .path() .iter() - .map(|x| (*x).to_string()) + .map(|x| x.to_string()) .collect::>() .join("/"); format!( - "http://localhost:3000{}?{}", - path, - utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET) + "http://localhost:3000{path}?{}", + utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET), ) } @@ -375,7 +373,7 @@ mod tests { http_tests::TestResponse { status_code: 400, body: None, - content_type: "application/json".to_string(), + content_type: "application/json".into(), } } diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index e3ff1bec..e715609e 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_rocket" version = "0.9.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "`juniper` GraphQL integration with `rocket`." license = "BSD-2-Clause" authors = [ @@ -17,7 +18,7 @@ keywords = ["apollo", "graphql", "juniper", "rocket"] exclude = ["/examples/", "/tests/", "/release.toml"] [dependencies] -futures = "0.3.1" +futures = "0.3.22" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } rocket = { version = "=0.5.0-rc.2", default-features = false } serde_json = "1.0.2" diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index e6e60bee..fea4e27e 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -242,9 +242,9 @@ where fn push_value(ctx: &mut Self::Context, field: ValueField<'f>) { match field.name.key().map(|key| key.as_str()) { - Some("query") => ctx.query(field.value.to_owned()), - Some("operation_name") => ctx.operation_name(field.value.to_owned()), - Some("variables") => ctx.variables(field.value.to_owned()), + Some("query") => ctx.query(field.value.into()), + Some("operation_name") => ctx.operation_name(field.value.into()), + Some("variables") => ctx.variables(field.value.into()), Some(key) => { if ctx.opts.strict { let error = Error::from(ErrorKind::Unknown).with_name(key); @@ -318,13 +318,13 @@ where let mut reader = data.open(limit); let mut body = String::new(); if let Err(e) = reader.read_to_string(&mut body).await { - return Failure((Status::InternalServerError, format!("{:?}", e))); + return Failure((Status::InternalServerError, format!("{e:?}"))); } Success(GraphQLRequest(if is_json { match serde_json::from_str(&body) { Ok(req) => req, - Err(e) => return Failure((Status::BadRequest, format!("{}", e))), + Err(e) => return Failure((Status::BadRequest, e.to_string())), } } else { GraphQLBatchRequest::Single(http::GraphQLRequest::new(body, None, None)) @@ -437,7 +437,7 @@ mod fromform_tests { check_error( "query=test&variables=NOT_JSON", vec![Error::from(ErrorKind::Validation(Cow::Owned( - "expected value at line 1 column 1".to_owned(), + "expected value at line 1 column 1".into(), ))) .with_name("variables")], false, @@ -451,7 +451,7 @@ mod fromform_tests { assert!(result.is_ok()); let variables = ::serde_json::from_str::(r#"{"foo":"bar"}"#).unwrap(); let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new( - "test".to_string(), + "test".into(), None, Some(variables), ))); @@ -465,7 +465,7 @@ mod fromform_tests { )); let variables = ::serde_json::from_str::(r#"{"foo":"x y&? z"}"#).unwrap(); let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new( - "test".to_string(), + "test".into(), None, Some(variables), ))); @@ -479,8 +479,8 @@ mod fromform_tests { )); assert!(result.is_ok()); let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new( - "%foo bar baz&?".to_string(), - Some("test".to_string()), + "%foo bar baz&?".into(), + Some("test".into()), None, ))); assert_eq!(result.unwrap(), expected); diff --git a/juniper_rocket/tests/custom_response_tests.rs b/juniper_rocket/tests/custom_response_tests.rs index 050be566..72893703 100644 --- a/juniper_rocket/tests/custom_response_tests.rs +++ b/juniper_rocket/tests/custom_response_tests.rs @@ -3,5 +3,5 @@ use rocket::http::Status; #[test] fn test_graphql_response_is_public() { - let _ = GraphQLResponse(Status::Unauthorized, "Unauthorized".to_string()); + let _ = GraphQLResponse(Status::Unauthorized, "Unauthorized".into()); } diff --git a/juniper_subscriptions/Cargo.toml b/juniper_subscriptions/Cargo.toml index ddea6d98..8c6a14a5 100644 --- a/juniper_subscriptions/Cargo.toml +++ b/juniper_subscriptions/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_subscriptions" version = "0.17.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "Juniper `SubscriptionCoordinator` and `SubscriptionConnection` implementations." license = "BSD-2-Clause" authors = ["nWacky "] @@ -14,7 +15,7 @@ keywords = ["graphql", "server", "subscription", "web", "websocket"] exclude = ["/release.toml"] [dependencies] -futures = "0.3.1" +futures = "0.3.22" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } [dev-dependencies] diff --git a/juniper_subscriptions/src/lib.rs b/juniper_subscriptions/src/lib.rs index df6c5fe7..3d5d77bb 100644 --- a/juniper_subscriptions/src/lib.rs +++ b/juniper_subscriptions/src/lib.rs @@ -3,7 +3,6 @@ #![deny(warnings)] use std::{ - iter::FromIterator, pin::Pin, task::{self, Poll}, }; @@ -63,8 +62,7 @@ where S: ScalarValue + Send + Sync + 'a, { type Connection = Connection<'a, S>; - - type Error = GraphQLError<'a>; + type Error = GraphQLError; fn subscribe( &'a self, diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index b49e379d..361b00f5 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "juniper_warp" version = "0.8.0-dev" -edition = "2018" +edition = "2021" +rust-version = "1.62" description = "`juniper` GraphQL integration with `warp`." license = "BSD-2-Clause" authors = ["Tom Houlé "] @@ -22,7 +23,7 @@ subscriptions = ["juniper_graphql_ws"] [dependencies] anyhow = "1.0" -futures = "0.3.1" +futures = "0.3.22" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } juniper_graphql_ws = { version = "0.4.0-dev", path = "../juniper_graphql_ws", optional = true } serde = { version = "1.0.75", features = ["derive"] } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index e7686b8d..b30b007b 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -44,7 +44,7 @@ use warp::{body, filters::BoxedFilter, http, hyper::body::Bytes, query, Filter}; /// format!( /// "good morning {}, the app state is {:?}", /// context.1, -/// context.0 +/// context.0, /// ) /// } /// } @@ -108,7 +108,7 @@ where let schema = post_graphql_schema.clone(); async move { let query = str::from_utf8(body.as_ref()) - .map_err(|e| anyhow!("Request body query is not a valid UTF-8 string: {}", e))?; + .map_err(|e| anyhow!("Request body query is not a valid UTF-8 string: {e}"))?; let req = GraphQLRequest::new(query.into(), None, None); let resp = req.execute(&schema, &context).await; @@ -192,7 +192,7 @@ where async move { let res = task::spawn_blocking(move || { let query = str::from_utf8(body.as_ref()) - .map_err(|e| anyhow!("Request body is not a valid UTF-8 string: {}", e))?; + .map_err(|e| anyhow!("Request body is not a valid UTF-8 string: {e}"))?; let req = GraphQLRequest::new(query.into(), None, None); let resp = req.execute_sync(&schema, &context); @@ -359,7 +359,7 @@ pub mod subscriptions { struct Message(warp::ws::Message); - impl std::convert::TryFrom for ClientMessage { + impl TryFrom for ClientMessage { type Error = serde_json::Error; fn try_from(msg: Message) -> serde_json::Result { @@ -381,8 +381,8 @@ pub mod subscriptions { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Warp(e) => write!(f, "warp error: {}", e), - Self::Serde(e) => write!(f, "serde error: {}", e), + Self::Warp(e) => write!(f, "warp error: {e}"), + Self::Serde(e) => write!(f, "serde error: {e}"), } } } @@ -711,7 +711,7 @@ mod tests_http_harness { const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); - let url = Url::parse(&format!("http://localhost:3000{}", url)).expect("url to parse"); + let url = Url::parse(&format!("http://localhost:3000{url}")).expect("url to parse"); let url: String = utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET) .into_iter() @@ -721,7 +721,7 @@ mod tests_http_harness { self.make_request( warp::test::request() .method("GET") - .path(&format!("/?{}", url)), + .path(&format!("/?{url}")), ) } @@ -756,7 +756,7 @@ mod tests_http_harness { .expect("missing content-type header in warp response") .to_str() .expect("invalid content-type string") - .to_owned(), + .into(), } } diff --git a/tests/codegen/Cargo.toml b/tests/codegen/Cargo.toml index eb513bf3..eff4f4ad 100644 --- a/tests/codegen/Cargo.toml +++ b/tests/codegen/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "juniper_codegen_tests" version = "0.0.0" -edition = "2018" +edition = "2021" publish = false [dependencies] -futures = "0.3.1" -juniper = { path = "../../juniper" } +rustversion = "1.0" [dev-dependencies] -serde_json = { version = "1.0" } +futures = "0.3" +juniper = { path = "../../juniper" } +serde_json = "1.0" tokio = { version = "1.0", features = ["rt", "time", "macros"] } -trybuild = "1.0.25" +trybuild = "1.0.63" diff --git a/tests/codegen/fail/enum/derive_duplicated_value_names.rs b/tests/codegen/fail/enum/derive_duplicated_value_names.rs new file mode 100644 index 00000000..3044f379 --- /dev/null +++ b/tests/codegen/fail/enum/derive_duplicated_value_names.rs @@ -0,0 +1,10 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum)] +enum Test { + Test, + #[graphql(name = "TEST")] + Test1, +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_duplicated_value_names.stderr b/tests/codegen/fail/enum/derive_duplicated_value_names.stderr new file mode 100644 index 00000000..8b5ca830 --- /dev/null +++ b/tests/codegen/fail/enum/derive_duplicated_value_names.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum expected all GraphQL enum values to have unique names + --> fail/enum/derive_duplicated_value_names.rs:5:5 + | +5 | / Test, +6 | | #[graphql(name = "TEST")] +7 | | Test1, + | |__________^ diff --git a/tests/codegen/fail/enum/derive_name_double_underscored.rs b/tests/codegen/fail/enum/derive_name_double_underscored.rs new file mode 100644 index 00000000..11c9d708 --- /dev/null +++ b/tests/codegen/fail/enum/derive_name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum)] +enum __Test { + Test, +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_name_double_underscored.stderr b/tests/codegen/fail/enum/derive_name_double_underscored.stderr new file mode 100644 index 00000000..36955ef3 --- /dev/null +++ b/tests/codegen/fail/enum/derive_name_double_underscored.stderr @@ -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. + --> fail/enum/derive_name_double_underscored.rs:4:6 + | +4 | enum __Test { + | ^^^^^^ + | + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/enum/derive_no_fields.rs b/tests/codegen/fail/enum/derive_no_fields.rs deleted file mode 100644 index f7df4e7d..00000000 --- a/tests/codegen/fail/enum/derive_no_fields.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(juniper::GraphQLEnum)] -pub enum Test {} - -fn main() { } diff --git a/tests/codegen/fail/enum/derive_no_fields.stderr b/tests/codegen/fail/enum/derive_no_fields.stderr deleted file mode 100644 index 63715639..00000000 --- a/tests/codegen/fail/enum/derive_no_fields.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: GraphQL enum expects at least one field - --> fail/enum/derive_no_fields.rs:2:1 - | -2 | pub enum Test {} - | ^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Enums diff --git a/tests/codegen/fail/enum/derive_no_values.rs b/tests/codegen/fail/enum/derive_no_values.rs new file mode 100644 index 00000000..fe36fe44 --- /dev/null +++ b/tests/codegen/fail/enum/derive_no_values.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum)] +enum Test {} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_no_values.stderr b/tests/codegen/fail/enum/derive_no_values.stderr new file mode 100644 index 00000000..b9b26cae --- /dev/null +++ b/tests/codegen/fail/enum/derive_no_values.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum expected at least 1 non-ignored enum variant + --> fail/enum/derive_no_values.rs:3:10 + | +3 | #[derive(GraphQLEnum)] + | ^^^^^^^^^^^ + | + = note: this error originates in the derive macro `GraphQLEnum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/enum/derive_variant_with_field.rs b/tests/codegen/fail/enum/derive_variant_with_field.rs new file mode 100644 index 00000000..61ddd796 --- /dev/null +++ b/tests/codegen/fail/enum/derive_variant_with_field.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum)] +enum Test { + Variant(i32), +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_variant_with_field.stderr b/tests/codegen/fail/enum/derive_variant_with_field.stderr new file mode 100644 index 00000000..be3bc256 --- /dev/null +++ b/tests/codegen/fail/enum/derive_variant_with_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum no fields allowed for non-ignored variants + --> fail/enum/derive_variant_with_field.rs:5:12 + | +5 | Variant(i32), + | ^^^^^ + | + = note: https://spec.graphql.org/October2021#sec-Enums diff --git a/tests/codegen/fail/enum/derive_wrong_item.rs b/tests/codegen/fail/enum/derive_wrong_item.rs new file mode 100644 index 00000000..e31351f3 --- /dev/null +++ b/tests/codegen/fail/enum/derive_wrong_item.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum)] +struct Test {} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_wrong_item.stderr b/tests/codegen/fail/enum/derive_wrong_item.stderr new file mode 100644 index 00000000..3670fc04 --- /dev/null +++ b/tests/codegen/fail/enum/derive_wrong_item.stderr @@ -0,0 +1,5 @@ +error: GraphQL enum can only be derived on enums + --> fail/enum/derive_wrong_item.rs:4:1 + | +4 | struct Test {} + | ^^^^^^^^^^^^^^ diff --git a/tests/codegen/fail/input-object/derive_incompatible_field_type.rs b/tests/codegen/fail/input-object/derive_incompatible_field_type.rs new file mode 100644 index 00000000..b3e26444 --- /dev/null +++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.rs @@ -0,0 +1,13 @@ +use juniper::{GraphQLInputObject, GraphQLObject}; + +#[derive(GraphQLObject)] +struct ObjectA { + test: String, +} + +#[derive(GraphQLInputObject)] +struct Object { + field: ObjectA, +} + +fn main() {} diff --git a/tests/codegen/fail/input-object/derive_incompatible_object.stderr b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr similarity index 50% rename from tests/codegen/fail/input-object/derive_incompatible_object.stderr rename to tests/codegen/fail/input-object/derive_incompatible_field_type.stderr index e8e2a3cc..5ddabb55 100644 --- a/tests/codegen/fail/input-object/derive_incompatible_object.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied - --> fail/input-object/derive_incompatible_object.rs:8:12 + --> fail/input-object/derive_incompatible_field_type.rs:8:10 | -8 | field: ObjectA, - | ^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` +8 | #[derive(GraphQLInputObject)] + | ^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` | = help: the following other types implement trait `IsInputType`: <&T as IsInputType> @@ -14,12 +14,13 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied as IsInputType> <[T; N] as IsInputType> and 13 others + = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied - --> fail/input-object/derive_incompatible_object.rs:6:10 + --> fail/input-object/derive_incompatible_field_type.rs:8:10 | -6 | #[derive(juniper::GraphQLInputObject)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` +8 | #[derive(GraphQLInputObject)] + | ^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` | = help: the following other types implement trait `FromInputValue`: as FromInputValue> @@ -36,13 +37,13 @@ note: required by a bound in `Registry::<'r, S>::arg` | | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` - = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied - --> fail/input-object/derive_incompatible_object.rs:6:10 + --> fail/input-object/derive_incompatible_field_type.rs:8:10 | -6 | #[derive(juniper::GraphQLInputObject)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` +8 | #[derive(GraphQLInputObject)] + | ^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` | = help: the following other types implement trait `FromInputValue`: as FromInputValue> @@ -54,18 +55,22 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied <[T; N] as FromInputValue> > and 10 others - = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `GraphQLInputObject` (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 - --> fail/input-object/derive_incompatible_object.rs:6:10 +error[E0277]: the trait bound `ObjectA: ToInputValue<_>` is not satisfied + --> fail/input-object/derive_incompatible_field_type.rs:8:10 | -2 | struct ObjectA { - | -------------- method `to_input_value` not found for this -... -6 | #[derive(juniper::GraphQLInputObject)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA` +8 | #[derive(GraphQLInputObject)] + | ^^^^^^^^^^^^^^^^^^ the trait `ToInputValue<_>` is not implemented for `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: `ToInputValue` - = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the following other types implement trait `ToInputValue`: + <&'a T as ToInputValue> + <&'a [T] as ToInputValue> + <&'a str as ToInputValue> + as ToInputValue> + as ToInputValue> + > + > + > + and 14 others + = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/input-object/derive_incompatible_object.rs b/tests/codegen/fail/input-object/derive_incompatible_object.rs deleted file mode 100644 index 98cdb65b..00000000 --- a/tests/codegen/fail/input-object/derive_incompatible_object.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[derive(juniper::GraphQLObject)] -struct ObjectA { - test: String, -} - -#[derive(juniper::GraphQLInputObject)] -struct Object { - field: ObjectA, -} - -fn main() {} diff --git a/tests/codegen/fail/input-object/derive_no_fields.rs b/tests/codegen/fail/input-object/derive_no_fields.rs index eedbe26e..2861298f 100644 --- a/tests/codegen/fail/input-object/derive_no_fields.rs +++ b/tests/codegen/fail/input-object/derive_no_fields.rs @@ -1,4 +1,6 @@ -#[derive(juniper::GraphQLInputObject)] +use juniper::GraphQLInputObject; + +#[derive(GraphQLInputObject)] struct Object {} fn main() {} diff --git a/tests/codegen/fail/input-object/derive_no_fields.stderr b/tests/codegen/fail/input-object/derive_no_fields.stderr index 9362b428..4ab046c5 100644 --- a/tests/codegen/fail/input-object/derive_no_fields.stderr +++ b/tests/codegen/fail/input-object/derive_no_fields.stderr @@ -1,7 +1,5 @@ -error: GraphQL input object expects at least one field - --> fail/input-object/derive_no_fields.rs:2:1 +error: GraphQL input object expected at least 1 non-ignored field + --> fail/input-object/derive_no_fields.rs:4:15 | -2 | struct Object {} - | ^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Input-Objects +4 | struct Object {} + | ^^ diff --git a/tests/codegen/fail/input-object/derive_no_underscore.rs b/tests/codegen/fail/input-object/derive_no_underscore.rs index 71ab5b9d..46b86a3c 100644 --- a/tests/codegen/fail/input-object/derive_no_underscore.rs +++ b/tests/codegen/fail/input-object/derive_no_underscore.rs @@ -1,4 +1,6 @@ -#[derive(juniper::GraphQLInputObject)] +use juniper::GraphQLInputObject; + +#[derive(GraphQLInputObject)] struct Object { #[graphql(name = "__test")] test: String, diff --git a/tests/codegen/fail/input-object/derive_no_underscore.stderr b/tests/codegen/fail/input-object/derive_no_underscore.stderr index 86a6a9b2..98deeaa1 100644 --- a/tests/codegen/fail/input-object/derive_no_underscore.stderr +++ b/tests/codegen/fail/input-object/derive_no_underscore.stderr @@ -1,7 +1,8 @@ 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. - --> fail/input-object/derive_no_underscore.rs:3:15 + --> fail/input-object/derive_no_underscore.rs:5:5 | -3 | #[graphql(name = "__test")] - | ^^^^ +5 | / #[graphql(name = "__test")] +6 | | test: String, + | |________________^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/input-object/derive_unique_name.rs b/tests/codegen/fail/input-object/derive_unique_name.rs index ecaa8631..a0d9a5b2 100644 --- a/tests/codegen/fail/input-object/derive_unique_name.rs +++ b/tests/codegen/fail/input-object/derive_unique_name.rs @@ -1,4 +1,6 @@ -#[derive(juniper::GraphQLInputObject)] +use juniper::GraphQLInputObject; + +#[derive(GraphQLInputObject)] struct Object { test: String, #[graphql(name = "test")] diff --git a/tests/codegen/fail/input-object/derive_unique_name.stderr b/tests/codegen/fail/input-object/derive_unique_name.stderr index 066fdbe6..20a72cfa 100644 --- a/tests/codegen/fail/input-object/derive_unique_name.stderr +++ b/tests/codegen/fail/input-object/derive_unique_name.stderr @@ -1,9 +1,10 @@ -error: GraphQL input object does not allow fields with the same name - --> fail/input-object/derive_unique_name.rs:4:5 +error: GraphQL input object expected all fields to have unique names + --> fail/input-object/derive_unique_name.rs:4:15 | -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 +4 | struct Object { + | _______________^ +5 | | test: String, +6 | | #[graphql(name = "test")] +7 | | test2: String, +8 | | } + | |_^ diff --git a/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs index dfe2d7b5..e2ed37ea 100644 --- a/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs +++ b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs @@ -7,7 +7,7 @@ pub struct ObjA { #[graphql_object(impl = CharacterValue)] impl ObjA { fn id(&self, is_present: bool) -> &str { - is_present.then(|| self.id.as_str()).unwrap_or("missing") + is_present.then_some(&*self.id).unwrap_or("missing") } } diff --git a/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr index a1b8ce2f..5d03e44c 100644 --- a/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr +++ b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 16 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 16 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_cyclic_impl.rs b/tests/codegen/fail/interface/struct/attr_cyclic_impl.rs new file mode 100644 index 00000000..7681972a --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_cyclic_impl.rs @@ -0,0 +1,13 @@ +use juniper::graphql_interface; + +#[graphql_interface(impl = Node2Value, for = Node2Value)] +struct Node1 { + id: String, +} + +#[graphql_interface(impl = Node1Value, for = Node1Value)] +struct Node2 { + id: String, +} + +fn main() {} diff --git a/tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr b/tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr new file mode 100644 index 00000000..ed809a5a --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr @@ -0,0 +1,26 @@ +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/struct/attr_cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/struct/attr_cyclic_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/struct/attr_cyclic_impl.rs:1:1 + | +1 | / use juniper::graphql_interface; +2 | | +3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)] +4 | | struct Node1 { +... | +12 | | +13 | | fn main() {} + | |____________^ diff --git a/tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr b/tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr index 31648f4e..85e57564 100644 --- a/tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr +++ b/tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | __id: String, | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr b/tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr index b6a25f24..109f2b7c 100644 --- a/tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr +++ b/tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr @@ -9,4 +9,4 @@ error: GraphQL interface must have a different name for each field 9 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr index c87ad2e0..ce5a6101 100644 --- a/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr +++ b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr @@ -9,3 +9,27 @@ error[E0412]: cannot find type `CharacterValue` in this scope | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr index da24362c..210ab346 100644 --- a/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr +++ b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -18,4 +18,4 @@ error[E0119]: conflicting implementations of trait ` fail/interface/struct/attr_missing_field.rs:11:5 @@ -12,7 +12,7 @@ error[E0080]: erroneous constant used 11 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/attr_missing_field.rs:11:5 @@ -20,23 +20,7 @@ error[E0080]: erroneous constant used 11 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/attr_missing_field.rs:11:5 - | -11 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/attr_missing_field.rs:11:5 - | -11 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/attr_missing_field.rs:11:5 @@ -44,7 +28,7 @@ error[E0080]: erroneous constant used 11 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/attr_missing_field.rs:11:5 @@ -52,7 +36,300 @@ error[E0080]: erroneous constant used 11 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/attr_missing_field.rs:11:5 @@ -60,4 +337,648 @@ error[E0080]: evaluation of constant value failed 11 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr b/tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr index b7008d17..eddbb9b3 100644 --- a/tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr +++ b/tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 3 | #[derive(GraphQLObject)] | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/attr_missing_for_attr.rs:3:10 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr b/tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr index f03491d3..cfccb3cb 100644 --- a/tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr +++ b/tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/attr_missing_impl_attr.rs:8:1 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.rs b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.rs new file mode 100644 index 00000000..590d1517 --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.rs @@ -0,0 +1,18 @@ +use juniper::graphql_interface; + +#[graphql_interface(for = Node2Value)] +struct Node1 { + id: String, +} + +#[graphql_interface(impl = Node1Value, for = Node3Value)] +struct Node2 { + id: String, +} + +#[graphql_interface(impl = Node2Value)] +struct Node3 { + id: String, +} + +fn main() {} diff --git a/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.stderr b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.stderr new file mode 100644 index 00000000..0eb3178c --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_transitive_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/attr_missing_transitive_impl.rs:8:46 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_transitive_impls` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr b/tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr index 15db17f6..df34c85e 100644 --- a/tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr +++ b/tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | struct __Character { | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/struct/attr_no_fields.stderr b/tests/codegen/fail/interface/struct/attr_no_fields.stderr index 6920674e..f8fc279a 100644 --- a/tests/codegen/fail/interface/struct/attr_no_fields.stderr +++ b/tests/codegen/fail/interface/struct/attr_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL interface must have at least one field 4 | struct Character {} | ^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr b/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr index c5d9903c..480c7b1e 100644 --- a/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr +++ b/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 11 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/attr_non_subtype_return.rs:11:5 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 11 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_unnamed_field.stderr b/tests/codegen/fail/interface/struct/attr_unnamed_field.stderr index 6fa918e2..6ad84969 100644 --- a/tests/codegen/fail/interface/struct/attr_unnamed_field.stderr +++ b/tests/codegen/fail/interface/struct/attr_unnamed_field.stderr @@ -4,4 +4,4 @@ error: GraphQL interface expected named struct field 4 | struct Character(i32); | ^^^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs index 5636cc17..3a7d192c 100644 --- a/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs +++ b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs @@ -7,7 +7,7 @@ pub struct ObjA { #[graphql_object(impl = CharacterValue)] impl ObjA { fn id(&self, is_present: bool) -> &str { - is_present.then(|| self.id.as_str()).unwrap_or("missing") + is_present.then_some(&*self.id).unwrap_or("missing") } } diff --git a/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr index aa1fd8ee..f6e828fc 100644 --- a/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr +++ b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 17 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 17 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_cyclic_impl.rs b/tests/codegen/fail/interface/struct/derive_cyclic_impl.rs new file mode 100644 index 00000000..56d77cc6 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_cyclic_impl.rs @@ -0,0 +1,15 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +#[graphql(impl = Node2Value, for = Node2Value)] +struct Node1 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node1Value, for = Node1Value)] +struct Node2 { + id: String, +} + +fn main() {} diff --git a/tests/codegen/fail/interface/struct/derive_cyclic_impl.stderr b/tests/codegen/fail/interface/struct/derive_cyclic_impl.stderr new file mode 100644 index 00000000..28f960eb --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_cyclic_impl.stderr @@ -0,0 +1,26 @@ +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/struct/derive_cyclic_impl.rs:4:36 + | +4 | #[graphql(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/struct/derive_cyclic_impl.rs:10:36 + | +10 | #[graphql(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/struct/derive_cyclic_impl.rs:1:1 + | +1 | / use juniper::GraphQLInterface; +2 | | +3 | | #[derive(GraphQLInterface)] +4 | | #[graphql(impl = Node2Value, for = Node2Value)] +... | +14 | | +15 | | fn main() {} + | |____________^ diff --git a/tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr b/tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr index 37b88b68..f8330549 100644 --- a/tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr +++ b/tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | __id: String, | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr b/tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr index a0078989..63d03bc9 100644 --- a/tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr +++ b/tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr @@ -9,4 +9,4 @@ error: GraphQL interface must have a different name for each field 9 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr index a4a80378..926c96b1 100644 --- a/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr +++ b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr @@ -9,3 +9,27 @@ error[E0412]: cannot find type `CharacterValue` in this scope | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr index fde533c1..de35d399 100644 --- a/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr +++ b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr @@ -18,4 +18,4 @@ error[E0119]: conflicting implementations of trait ` fail/interface/struct/derive_missing_field.rs:12:5 @@ -12,7 +12,7 @@ error[E0080]: erroneous constant used 12 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/derive_missing_field.rs:12:5 @@ -20,23 +20,7 @@ error[E0080]: erroneous constant used 12 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/derive_missing_field.rs:12:5 - | -12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/derive_missing_field.rs:12:5 - | -12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/derive_missing_field.rs:12:5 @@ -44,7 +28,7 @@ error[E0080]: erroneous constant used 12 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/struct/derive_missing_field.rs:12:5 @@ -52,7 +36,300 @@ error[E0080]: erroneous constant used 12 | id: String, | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/derive_missing_field.rs:12:5 @@ -60,4 +337,648 @@ error[E0080]: evaluation of constant value failed 12 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr b/tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr index 9f394b97..89c02448 100644 --- a/tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr +++ b/tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 3 | #[derive(GraphQLObject)] | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/derive_missing_for_attr.rs:3:10 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr b/tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr index e092494a..7a7a1018 100644 --- a/tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr +++ b/tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 8 | #[derive(GraphQLInterface)] | ^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/derive_missing_impl_attr.rs:8:10 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the derive macro `GraphQLInterface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.rs b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.rs new file mode 100644 index 00000000..446aae21 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.rs @@ -0,0 +1,21 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +#[graphql(for = Node2Value)] +struct Node1 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node1Value, for = Node3Value)] +struct Node2 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node2Value)] +struct Node3 { + id: String, +} + +fn main() {} diff --git a/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.stderr b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.stderr new file mode 100644 index 00000000..8ed89a93 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_transitive_impl.rs:10:36 + | +10 | #[graphql(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/derive_missing_transitive_impl.rs:10:36 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_transitive_impls` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr b/tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr index 0996adac..9b41f141 100644 --- a/tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr +++ b/tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | struct __Character { | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/struct/derive_no_fields.stderr b/tests/codegen/fail/interface/struct/derive_no_fields.stderr index d0718a6d..742c337a 100644 --- a/tests/codegen/fail/interface/struct/derive_no_fields.stderr +++ b/tests/codegen/fail/interface/struct/derive_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL interface must have at least one field 4 | struct Character {} | ^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr b/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr index b62b6b0f..87095cf3 100644 --- a/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr +++ b/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 12 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/struct/derive_non_subtype_return.rs:12:5 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 12 | id: String, | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/derive_unnamed_field.stderr b/tests/codegen/fail/interface/struct/derive_unnamed_field.stderr index 8a826c10..9783cb53 100644 --- a/tests/codegen/fail/interface/struct/derive_unnamed_field.stderr +++ b/tests/codegen/fail/interface/struct/derive_unnamed_field.stderr @@ -4,4 +4,4 @@ error: GraphQL interface expected named struct field 4 | struct Character(i32); | ^^^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs index c69f0f66..d8c4060c 100644 --- a/tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs +++ b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs @@ -7,7 +7,7 @@ pub struct ObjA { #[graphql_object(impl = CharacterValue)] impl ObjA { fn id(&self, is_present: bool) -> &str { - is_present.then(|| self.id.as_str()).unwrap_or("missing") + is_present.then_some(&*self.id).unwrap_or("missing") } } diff --git a/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr index 4817ef41..29e48523 100644 --- a/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr +++ b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/argument_double_underscored.stderr b/tests/codegen/fail/interface/trait/argument_double_underscored.stderr index 81723140..4cf82f0a 100644 --- a/tests/codegen/fail/interface/trait/argument_double_underscored.stderr +++ b/tests/codegen/fail/interface/trait/argument_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | fn id(&self, __num: i32) -> &str; | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr b/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr index c8a00ea3..2d2feacb 100644 --- a/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr @@ -1,8 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/trait/argument_wrong_default_array.rs:3:1 + --> fail/interface/trait/argument_wrong_default_array.rs:5:41 | 3 | #[graphql_interface] - | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | -------------------- required by a bound introduced by this call +4 | trait Character { +5 | fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool; + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> @@ -14,5 +17,4 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied <[u128; 1] as From> <[u128; 2] as From> and 7 others - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: required for `[bool; 3]` to implement `Into<[bool; 2]>` diff --git a/tests/codegen/fail/interface/trait/cyclic_impl.rs b/tests/codegen/fail/interface/trait/cyclic_impl.rs new file mode 100644 index 00000000..6f5b7947 --- /dev/null +++ b/tests/codegen/fail/interface/trait/cyclic_impl.rs @@ -0,0 +1,13 @@ +use juniper::graphql_interface; + +#[graphql_interface(impl = Node2Value, for = Node2Value)] +trait Node1 { + fn id(&self) -> &str; +} + +#[graphql_interface(impl = Node1Value, for = Node1Value)] +trait Node2 { + fn id() -> String; +} + +fn main() {} diff --git a/tests/codegen/fail/interface/trait/cyclic_impl.stderr b/tests/codegen/fail/interface/trait/cyclic_impl.stderr new file mode 100644 index 00000000..9865861f --- /dev/null +++ b/tests/codegen/fail/interface/trait/cyclic_impl.stderr @@ -0,0 +1,26 @@ +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/trait/cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/trait/cyclic_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/trait/cyclic_impl.rs:1:1 + | +1 | / use juniper::graphql_interface; +2 | | +3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)] +4 | | trait Node1 { +... | +12 | | +13 | | fn main() {} + | |____________^ diff --git a/tests/codegen/fail/interface/trait/field_double_underscored.stderr b/tests/codegen/fail/interface/trait/field_double_underscored.stderr index 979daae6..3069e574 100644 --- a/tests/codegen/fail/interface/trait/field_double_underscored.stderr +++ b/tests/codegen/fail/interface/trait/field_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | fn __id(&self) -> &str; | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/trait/fields_duplicate.stderr b/tests/codegen/fail/interface/trait/fields_duplicate.stderr index e7fc5080..4750e958 100644 --- a/tests/codegen/fail/interface/trait/fields_duplicate.stderr +++ b/tests/codegen/fail/interface/trait/fields_duplicate.stderr @@ -9,4 +9,4 @@ error: GraphQL interface must have a different name for each field 9 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr index 5d546902..0dc684f7 100644 --- a/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr +++ b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr @@ -9,3 +9,27 @@ error[E0412]: cannot find type `CharacterValue` in this scope | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr index 74b695d8..052d6585 100644 --- a/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -18,4 +18,4 @@ error[E0119]: conflicting implementations of trait ` &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/trait/missing_field.rs:11:8 @@ -12,7 +12,7 @@ error[E0080]: erroneous constant used 11 | fn id(&self) -> &str; | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/trait/missing_field.rs:11:8 @@ -20,23 +20,7 @@ error[E0080]: erroneous constant used 11 | fn id(&self) -> &str; | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/missing_field.rs:11:8 - | -11 | fn id(&self) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/missing_field.rs:11:8 - | -11 | fn id(&self) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/trait/missing_field.rs:11:8 @@ -44,7 +28,7 @@ error[E0080]: erroneous constant used 11 | fn id(&self) -> &str; | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: erroneous constant used --> fail/interface/trait/missing_field.rs:11:8 @@ -52,7 +36,300 @@ error[E0080]: erroneous constant used 11 | fn id(&self) -> &str; | ^^ referenced constant has errors | - = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ + | | + | referenced constant has errors + | inside ` as reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/trait/missing_field.rs:11:8 @@ -60,4 +337,648 @@ error[E0080]: evaluation of constant value failed 11 | fn id(&self) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/missing_field_argument.stderr b/tests/codegen/fail/interface/trait/missing_field_argument.stderr index 276222f7..d292529c 100644 --- a/tests/codegen/fail/interface/trait/missing_field_argument.stderr +++ b/tests/codegen/fail/interface/trait/missing_field_argument.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self, is_present: bool) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/trait/missing_field_argument.rs:16:8 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self, is_present: bool) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/missing_for_attr.stderr b/tests/codegen/fail/interface/trait/missing_for_attr.stderr index da011ad9..a71e07c3 100644 --- a/tests/codegen/fail/interface/trait/missing_for_attr.stderr +++ b/tests/codegen/fail/interface/trait/missing_for_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 3 | #[derive(GraphQLObject)] | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/trait/missing_for_attr.rs:3:10 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/missing_impl_attr.stderr b/tests/codegen/fail/interface/trait/missing_impl_attr.stderr index a82878d6..d502fd0e 100644 --- a/tests/codegen/fail/interface/trait/missing_impl_attr.stderr +++ b/tests/codegen/fail/interface/trait/missing_impl_attr.stderr @@ -4,4 +4,4 @@ error[E0080]: evaluation of constant value failed 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/trait/missing_impl_attr.rs:8:1 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/missing_transitive_impl.rs b/tests/codegen/fail/interface/trait/missing_transitive_impl.rs new file mode 100644 index 00000000..2bc08fb0 --- /dev/null +++ b/tests/codegen/fail/interface/trait/missing_transitive_impl.rs @@ -0,0 +1,18 @@ +use juniper::graphql_interface; + +#[graphql_interface(for = Node2Value)] +trait Node1 { + fn id() -> String; +} + +#[graphql_interface(impl = Node1Value, for = Node3Value)] +trait Node2 { + fn id(&self) -> &str; +} + +#[graphql_interface(impl = Node2Value)] +trait Node3 { + fn id() -> &'static str; +} + +fn main() {} diff --git a/tests/codegen/fail/interface/trait/missing_transitive_impl.stderr b/tests/codegen/fail/interface/trait/missing_transitive_impl.stderr new file mode 100644 index 00000000..16767ee9 --- /dev/null +++ b/tests/codegen/fail/interface/trait/missing_transitive_impl.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_transitive_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/trait/missing_transitive_impl.rs:8:46 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_transitive_impls` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/name_double_underscored.stderr b/tests/codegen/fail/interface/trait/name_double_underscored.stderr index 1fa999a4..35640240 100644 --- a/tests/codegen/fail/interface/trait/name_double_underscored.stderr +++ b/tests/codegen/fail/interface/trait/name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | trait __Character { | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/interface/trait/no_fields.stderr b/tests/codegen/fail/interface/trait/no_fields.stderr index 63c7d296..2638dc9d 100644 --- a/tests/codegen/fail/interface/trait/no_fields.stderr +++ b/tests/codegen/fail/interface/trait/no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL interface must have at least one field 4 | trait Character {} | ^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/tests/codegen/fail/interface/trait/non_subtype_return.stderr b/tests/codegen/fail/interface/trait/non_subtype_return.stderr index 92e123f2..3c9d1bdb 100644 --- a/tests/codegen/fail/interface/trait/non_subtype_return.stderr +++ b/tests/codegen/fail/interface/trait/non_subtype_return.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 11 | fn id(&self) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/trait/non_subtype_return.rs:11:8 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 11 | fn id(&self) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/wrong_argument_type.stderr b/tests/codegen/fail/interface/trait/wrong_argument_type.stderr index 3a47b725..36c24e4f 100644 --- a/tests/codegen/fail/interface/trait/wrong_argument_type.stderr +++ b/tests/codegen/fail/interface/trait/wrong_argument_type.stderr @@ -4,7 +4,7 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self, is_present: bool) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed --> fail/interface/trait/wrong_argument_type.rs:16:8 @@ -12,4 +12,4 @@ error[E0080]: evaluation of constant value failed 16 | fn id(&self, is_present: bool) -> &str; | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/object/argument_double_underscored.stderr b/tests/codegen/fail/object/argument_double_underscored.stderr index 6ee3a756..ae33cf32 100644 --- a/tests/codegen/fail/object/argument_double_underscored.stderr +++ b/tests/codegen/fail/object/argument_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 7 | fn id(&self, __num: i32) -> &str { | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/object/argument_wrong_default_array.stderr b/tests/codegen/fail/object/argument_wrong_default_array.stderr index 76357529..1d92be56 100644 --- a/tests/codegen/fail/object/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/object/argument_wrong_default_array.stderr @@ -1,8 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/object/argument_wrong_default_array.rs:5:1 + --> fail/object/argument_wrong_default_array.rs:7:41 | 5 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | ----------------- required by a bound introduced by this call +6 | impl ObjA { +7 | fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> @@ -14,5 +17,4 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied <[u128; 1] as From> <[u128; 2] as From> and 7 others - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: required for `[bool; 3]` to implement `Into<[bool; 2]>` diff --git a/tests/codegen/fail/object/attr_fields_duplicate.stderr b/tests/codegen/fail/object/attr_fields_duplicate.stderr index 3c6968b2..04304a3a 100644 --- a/tests/codegen/fail/object/attr_fields_duplicate.stderr +++ b/tests/codegen/fail/object/attr_fields_duplicate.stderr @@ -4,4 +4,4 @@ error: GraphQL object must have a different name for each field 6 | impl ObjA { | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/object/attr_name_double_underscored.stderr b/tests/codegen/fail/object/attr_name_double_underscored.stderr index 031654a2..a46fbda4 100644 --- a/tests/codegen/fail/object/attr_name_double_underscored.stderr +++ b/tests/codegen/fail/object/attr_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 6 | impl __Obj { | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/object/attr_no_fields.stderr b/tests/codegen/fail/object/attr_no_fields.stderr index 9ba0098b..bd3a050b 100644 --- a/tests/codegen/fail/object/attr_no_fields.stderr +++ b/tests/codegen/fail/object/attr_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL object must have at least one field 6 | impl Obj {} | ^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/object/derive_field_double_underscored.stderr b/tests/codegen/fail/object/derive_field_double_underscored.stderr index 6a6e6403..16b38b89 100644 --- a/tests/codegen/fail/object/derive_field_double_underscored.stderr +++ b/tests/codegen/fail/object/derive_field_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | __test: String, | ^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/object/derive_fields_duplicate.stderr b/tests/codegen/fail/object/derive_fields_duplicate.stderr index 18c08423..e7e01291 100644 --- a/tests/codegen/fail/object/derive_fields_duplicate.stderr +++ b/tests/codegen/fail/object/derive_fields_duplicate.stderr @@ -8,4 +8,4 @@ error: GraphQL object must have a different name for each field 8 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/object/derive_name_double_underscored.stderr b/tests/codegen/fail/object/derive_name_double_underscored.stderr index 809f7297..08793621 100644 --- a/tests/codegen/fail/object/derive_name_double_underscored.stderr +++ b/tests/codegen/fail/object/derive_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | struct __Obj { | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/object/derive_no_fields.stderr b/tests/codegen/fail/object/derive_no_fields.stderr index ef198b79..31f6812d 100644 --- a/tests/codegen/fail/object/derive_no_fields.stderr +++ b/tests/codegen/fail/object/derive_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL object must have at least one field 4 | struct Obj {} | ^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/subscription/argument_double_underscored.stderr b/tests/codegen/fail/subscription/argument_double_underscored.stderr index fd373703..dd83ba63 100644 --- a/tests/codegen/fail/subscription/argument_double_underscored.stderr +++ b/tests/codegen/fail/subscription/argument_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 11 | async fn id(&self, __num: i32) -> Stream<'static, &'static str> { | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/subscription/argument_wrong_default_array.stderr b/tests/codegen/fail/subscription/argument_wrong_default_array.stderr index ea5ccd3c..6b4e5257 100644 --- a/tests/codegen/fail/subscription/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/subscription/argument_wrong_default_array.stderr @@ -1,8 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/subscription/argument_wrong_default_array.rs:10:1 + --> fail/subscription/argument_wrong_default_array.rs:14:29 | 10 | #[graphql_subscription] - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | ----------------------- required by a bound introduced by this call +... +14 | #[graphql(default = [true, false, false])] input: [bool; 2], + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> @@ -14,5 +17,4 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied <[u128; 1] as From> <[u128; 2] as From> and 7 others - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: required for `[bool; 3]` to implement `Into<[bool; 2]>` diff --git a/tests/codegen/fail/subscription/field_not_async.stderr b/tests/codegen/fail/subscription/field_not_async.stderr index c2061179..93fa942b 100644 --- a/tests/codegen/fail/subscription/field_not_async.stderr +++ b/tests/codegen/fail/subscription/field_not_async.stderr @@ -4,5 +4,5 @@ error: GraphQL object synchronous resolvers are not supported 11 | fn id(&self) -> Stream<'static, bool> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects = note: Specify that this function is async: `async fn foo()` diff --git a/tests/codegen/fail/subscription/fields_duplicate.stderr b/tests/codegen/fail/subscription/fields_duplicate.stderr index 34773a15..28281e51 100644 --- a/tests/codegen/fail/subscription/fields_duplicate.stderr +++ b/tests/codegen/fail/subscription/fields_duplicate.stderr @@ -4,4 +4,4 @@ error: GraphQL object must have a different name for each field 10 | impl ObjA { | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/subscription/name_double_underscored.stderr b/tests/codegen/fail/subscription/name_double_underscored.stderr index 4752ecb0..d9fffd1b 100644 --- a/tests/codegen/fail/subscription/name_double_underscored.stderr +++ b/tests/codegen/fail/subscription/name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 10 | impl __Obj { | ^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/subscription/no_fields.stderr b/tests/codegen/fail/subscription/no_fields.stderr index 90d4eb11..6faaed2f 100644 --- a/tests/codegen/fail/subscription/no_fields.stderr +++ b/tests/codegen/fail/subscription/no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL object must have at least one field 6 | impl Obj {} | ^^^ | - = note: https://spec.graphql.org/June2018/#sec-Objects + = note: https://spec.graphql.org/October2021#sec-Objects diff --git a/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr b/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr index 9d913d22..17b5ec8d 100644 --- a/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr +++ b/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr @@ -4,4 +4,4 @@ error: GraphQL union variant `Human` already has external resolver function `res 6 | #[graphql(with = resolve_fn2)] | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/enum_name_double_underscored.stderr b/tests/codegen/fail/union/enum_name_double_underscored.stderr index be6d0e29..623186d8 100644 --- a/tests/codegen/fail/union/enum_name_double_underscored.stderr +++ b/tests/codegen/fail/union/enum_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | enum __Character { | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/union/enum_no_fields.stderr b/tests/codegen/fail/union/enum_no_fields.stderr index 9ab9c400..7e036c6a 100644 --- a/tests/codegen/fail/union/enum_no_fields.stderr +++ b/tests/codegen/fail/union/enum_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL union expects at least one union variant 4 | enum Character {} | ^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/enum_same_type_pretty.stderr b/tests/codegen/fail/union/enum_same_type_pretty.stderr index 8c432f3e..7b03ac3a 100644 --- a/tests/codegen/fail/union/enum_same_type_pretty.stderr +++ b/tests/codegen/fail/union/enum_same_type_pretty.stderr @@ -7,4 +7,4 @@ error: GraphQL union must have a different type for each union variant 7 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/enum_same_type_ugly.stderr b/tests/codegen/fail/union/enum_same_type_ugly.stderr index fbb313ad..37c11674 100644 --- a/tests/codegen/fail/union/enum_same_type_ugly.stderr +++ b/tests/codegen/fail/union/enum_same_type_ugly.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String` - --> $DIR/enum_same_type_ugly.rs:3:10 + --> fail/union/enum_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] | ^^^^^^^^^^^^ @@ -7,4 +7,4 @@ error[E0119]: conflicting implementations of trait ` fail/union/enum_wrong_variant_field.rs:10:6 @@ -12,4 +12,4 @@ error: GraphQL union enum allows only unnamed variants with a single field, e.g. 10 | A(Human, u8), | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/struct_name_double_underscored.stderr b/tests/codegen/fail/union/struct_name_double_underscored.stderr index 5c4d04e4..84b3e3a9 100644 --- a/tests/codegen/fail/union/struct_name_double_underscored.stderr +++ b/tests/codegen/fail/union/struct_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 5 | struct __Character; | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/union/struct_no_fields.stderr b/tests/codegen/fail/union/struct_no_fields.stderr index c26a9da1..e5e79101 100644 --- a/tests/codegen/fail/union/struct_no_fields.stderr +++ b/tests/codegen/fail/union/struct_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL union expects at least one union variant 4 | struct Character; | ^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/struct_same_type_ugly.stderr b/tests/codegen/fail/union/struct_same_type_ugly.stderr index 48a91be2..583006f6 100644 --- a/tests/codegen/fail/union/struct_same_type_ugly.stderr +++ b/tests/codegen/fail/union/struct_same_type_ugly.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String` - --> $DIR/struct_same_type_ugly.rs:3:10 + --> fail/union/struct_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] | ^^^^^^^^^^^^ @@ -7,4 +7,4 @@ error[E0119]: conflicting implementations of trait ` $WORKSPACE/juniper/src/executor/mod.rs | - | fn into(self, ctx: &'a C) -> FieldResult, S>; - | ^^^^ + | fn into_resolvable(self, ctx: &'a C) -> FieldResult, S>; + | ^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr b/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr index b81e5a67..c098337e 100644 --- a/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr +++ b/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr @@ -4,5 +4,5 @@ error: GraphQL union trait method `a` conflicts with the external resolver funct 5 | fn a(&self) -> Option<&Human>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions = note: use `#[graphql(ignore)]` attribute to ignore this trait method for union variants resolution diff --git a/tests/codegen/fail/union/trait_name_double_underscored.stderr b/tests/codegen/fail/union/trait_name_double_underscored.stderr index baf44bc8..afb1f11e 100644 --- a/tests/codegen/fail/union/trait_name_double_underscored.stderr +++ b/tests/codegen/fail/union/trait_name_double_underscored.stderr @@ -4,4 +4,4 @@ error: All types and directives defined within a schema must not have a name whi 4 | trait __Character { | ^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Schema + = note: https://spec.graphql.org/October2021#sec-Schema diff --git a/tests/codegen/fail/union/trait_no_fields.stderr b/tests/codegen/fail/union/trait_no_fields.stderr index b75d5ba6..c2f032da 100644 --- a/tests/codegen/fail/union/trait_no_fields.stderr +++ b/tests/codegen/fail/union/trait_no_fields.stderr @@ -4,4 +4,4 @@ error: GraphQL union expects at least one union variant 4 | trait Character {} | ^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/trait_same_type_pretty.stderr b/tests/codegen/fail/union/trait_same_type_pretty.stderr index 61e642b3..116a7113 100644 --- a/tests/codegen/fail/union/trait_same_type_pretty.stderr +++ b/tests/codegen/fail/union/trait_same_type_pretty.stderr @@ -7,4 +7,4 @@ error: GraphQL union must have a different type for each union variant 7 | | } | |_^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/trait_same_type_ugly.stderr b/tests/codegen/fail/union/trait_same_type_ugly.stderr index 9b7eabbd..0388fef3 100644 --- a/tests/codegen/fail/union/trait_same_type_ugly.stderr +++ b/tests/codegen/fail/union/trait_same_type_ugly.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String` - --> $DIR/trait_same_type_ugly.rs:3:1 + --> fail/union/trait_same_type_ugly.rs:3:1 | 3 | #[graphql_union] | ^^^^^^^^^^^^^^^^ @@ -7,4 +7,4 @@ error[E0119]: conflicting implementations of trait `<(dyn Character + std::marke | first implementation here | conflicting implementation for `std::string::String` | - = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` which comes from the expansion of the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/union/trait_with_attr_on_method.stderr b/tests/codegen/fail/union/trait_with_attr_on_method.stderr index 2402ab17..6e406214 100644 --- a/tests/codegen/fail/union/trait_with_attr_on_method.stderr +++ b/tests/codegen/fail/union/trait_with_attr_on_method.stderr @@ -4,5 +4,5 @@ error: GraphQL union cannot use #[graphql(with = ...)] attribute on a trait meth 5 | #[graphql(with = something)] | ^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions = note: instead use #[graphql(ignore)] on the method with #[graphql_union(on ... = ...)] on the trait itself diff --git a/tests/codegen/fail/union/trait_wrong_method_input_args.stderr b/tests/codegen/fail/union/trait_wrong_method_input_args.stderr index 44d447c5..74c68596 100644 --- a/tests/codegen/fail/union/trait_wrong_method_input_args.stderr +++ b/tests/codegen/fail/union/trait_wrong_method_input_args.stderr @@ -4,4 +4,4 @@ error: GraphQL union expects trait method to accept `&self` only and, optionally 5 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human>; | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/fail/union/trait_wrong_method_return_type.stderr b/tests/codegen/fail/union/trait_wrong_method_return_type.stderr index 87ee8ff1..1594d53a 100644 --- a/tests/codegen/fail/union/trait_wrong_method_return_type.stderr +++ b/tests/codegen/fail/union/trait_wrong_method_return_type.stderr @@ -4,4 +4,4 @@ error: GraphQL union expects trait method return type to be `Option<&VariantType 5 | fn a(&self) -> &Human; | ^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Unions + = note: https://spec.graphql.org/October2021#sec-Unions diff --git a/tests/codegen/src/lib.rs b/tests/codegen/src/lib.rs index 656b5e7a..abdcf203 100644 --- a/tests/codegen/src/lib.rs +++ b/tests/codegen/src/lib.rs @@ -1,38 +1,9 @@ // 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(()) -} - +#[rustversion::nightly] #[test] -fn test_failing_compiliation() { +fn test_failing_compilation() { 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(); + t.compile_fail("fail/**/*.rs"); } diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 2b2c3cc7..ad25a358 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -1,19 +1,18 @@ [package] name = "juniper_integration_tests" version = "0.0.0" -edition = "2018" +edition = "2021" publish = false -[dependencies] -chrono = "0.4" -derive_more = "0.99" -futures = "0.3" -juniper = { path = "../../juniper" } -juniper_subscriptions = { path = "../../juniper_subscriptions" } - [dev-dependencies] async-trait = "0.1.39" +chrono = { version = "0.4.20", default-features = false } +derive_more = "0.99" fnv = "1.0" +futures = "0.3" +itertools = "0.10" +juniper = { path = "../../juniper" } +juniper_subscriptions = { path = "../../juniper_subscriptions" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0", features = ["rt", "macros", "time"] } diff --git a/tests/integration/src/codegen/derive_enum.rs b/tests/integration/src/codegen/derive_enum.rs deleted file mode 100644 index acaa49a8..00000000 --- a/tests/integration/src/codegen/derive_enum.rs +++ /dev/null @@ -1,133 +0,0 @@ -use fnv::FnvHashMap; -use juniper::{ - graphql_input_value, DefaultScalarValue, FromInputValue, GraphQLEnum, GraphQLType, Registry, - ToInputValue, -}; - -pub struct CustomContext {} - -impl juniper::Context for CustomContext {} - -#[derive(GraphQLEnum, Debug, PartialEq)] -#[graphql(name = "Some", description = "enum descr")] -enum SomeEnum { - Regular, - #[graphql(name = "FULL", description = "field descr", deprecated = "depr")] - Full, -} - -#[derive(juniper::GraphQLEnum, Debug, PartialEq)] -#[graphql(rename = "none")] -enum NoRenameEnum { - OneVariant, - AnotherVariant, -} - -/// Enum doc. -#[derive(GraphQLEnum)] -enum DocEnum { - /// Variant doc. - Foo, -} - -/// Doc 1.\ -/// Doc 2. -/// -/// Doc 4. -#[derive(GraphQLEnum, Debug, PartialEq)] -enum MultiDocEnum { - /// Variant 1. - /// Variant 2. - Foo, -} - -/// This is not used as the description. -#[derive(GraphQLEnum, Debug, PartialEq)] -#[graphql(description = "enum override")] -enum OverrideDocEnum { - /// This is not used as the description. - #[graphql(description = "variant override")] - Foo, -} - -#[derive(GraphQLEnum)] -#[graphql(context = CustomContext, noasync)] -enum ContextEnum { - A, -} - -#[test] -fn test_derived_enum() { - // Ensure that rename works. - assert_eq!( - >::name(&()), - Some("Some") - ); - - // Ensure validity of meta info. - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = SomeEnum::meta(&(), &mut registry); - - assert_eq!(meta.name(), Some("Some")); - assert_eq!(meta.description(), Some("enum descr")); - - // Test no rename variant. - assert_eq!( - <_ as ToInputValue>::to_input_value(&NoRenameEnum::AnotherVariant), - graphql_input_value!("AnotherVariant"), - ); - - // Test Regular variant. - assert_eq!( - <_ as ToInputValue>::to_input_value(&SomeEnum::Regular), - graphql_input_value!("REGULAR"), - ); - assert_eq!( - FromInputValue::::from_input_value(&graphql_input_value!(REGULAR)), - Ok(SomeEnum::Regular), - ); - - // Test FULL variant. - assert_eq!( - <_ as ToInputValue>::to_input_value(&SomeEnum::Full), - graphql_input_value!("FULL"), - ); - assert_eq!( - FromInputValue::::from_input_value(&graphql_input_value!(FULL)), - Ok(SomeEnum::Full) - ); -} - -#[test] -fn test_doc_comment() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = DocEnum::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("Enum doc.")); -} - -#[test] -fn test_multi_doc_comment() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = MultiDocEnum::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4.")); -} - -#[test] -fn test_doc_comment_override() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = OverrideDocEnum::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("enum override")); -} - -fn test_context(_t: T) -where - T: GraphQLType, -{ - // empty -} - -#[test] -fn test_doc_custom_context() { - test_context(ContextEnum::A); - // test_context(OverrideDocEnum::Foo); does not work -} diff --git a/tests/integration/src/codegen/derive_input_object.rs b/tests/integration/src/codegen/derive_input_object.rs deleted file mode 100644 index f85b042e..00000000 --- a/tests/integration/src/codegen/derive_input_object.rs +++ /dev/null @@ -1,191 +0,0 @@ -use fnv::FnvHashMap; -use juniper::{ - graphql_input_value, marker, DefaultScalarValue, FieldError, FromInputValue, - GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, Registry, ToInputValue, -}; - -#[derive(GraphQLInputObject, Debug, PartialEq)] -#[graphql( - name = "MyInput", - description = "input descr", - scalar = DefaultScalarValue -)] -struct Input { - regular_field: String, - #[graphql(name = "haha", default = "33", description = "haha descr")] - c: i32, - - #[graphql(default)] - other: Option, -} - -#[derive(GraphQLInputObject, Debug, PartialEq)] -#[graphql(rename = "none")] -struct NoRenameInput { - regular_field: String, -} - -/// Object comment. -#[derive(GraphQLInputObject, Debug, PartialEq)] -struct DocComment { - /// Field comment. - regular_field: bool, -} - -/// Doc 1.\ -/// Doc 2. -/// -/// Doc 4. -#[derive(GraphQLInputObject, Debug, PartialEq)] -struct MultiDocComment { - /// Field 1. - /// Field 2. - regular_field: bool, -} - -/// This is not used as the description. -#[derive(GraphQLInputObject, Debug, PartialEq)] -#[graphql(description = "obj override")] -struct OverrideDocComment { - /// This is not used as the description. - #[graphql(description = "field override")] - regular_field: bool, -} - -#[derive(Debug, PartialEq)] -struct Fake; - -impl<'a> marker::IsInputType for &'a Fake {} - -impl<'a> FromInputValue for &'a Fake { - type Error = FieldError; - - fn from_input_value(_v: &InputValue) -> Result<&'a Fake, Self::Error> { - Err("This is fake".into()) - } -} - -impl<'a> ToInputValue for &'a Fake { - fn to_input_value(&self) -> InputValue { - graphql_input_value!("this is fake") - } -} - -impl<'a> GraphQLType for &'a Fake { - fn name(_: &()) -> Option<&'static str> { - None - } - fn meta<'r>(_: &(), registry: &mut Registry<'r>) -> juniper::meta::MetaType<'r> - where - DefaultScalarValue: 'r, - { - let meta = registry.build_enum_type::<&'a Fake>( - &(), - &[juniper::meta::EnumValue { - name: "fake".to_string(), - description: None, - deprecation_status: juniper::meta::DeprecationStatus::Current, - }], - ); - meta.into_meta() - } -} - -impl<'a> GraphQLValue for &'a Fake { - type Context = (); - type TypeInfo = (); - - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { - ::name(info) - } -} - -#[derive(GraphQLInputObject, Debug, PartialEq)] -#[graphql(scalar = DefaultScalarValue)] -struct WithLifetime<'a> { - regular_field: &'a Fake, -} - -#[test] -fn test_derived_input_object() { - assert_eq!( - >::name(&()), - Some("MyInput") - ); - - // Validate meta info. - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = Input::meta(&(), &mut registry); - assert_eq!(meta.name(), Some("MyInput")); - assert_eq!(meta.description(), Some("input descr")); - - // Test default value injection. - - let input_no_defaults = graphql_input_value!({ - "regularField": "a", - }); - let output_no_defaults = Input::from_input_value(&input_no_defaults).unwrap(); - assert_eq!( - output_no_defaults, - Input { - regular_field: "a".into(), - c: 33, - other: None, - }, - ); - - // Test with all values supplied. - - let input: InputValue = ::serde_json::from_value(serde_json::json!({ - "regularField": "a", - "haha": 55, - "other": true, - })) - .unwrap(); - - let output: Input = FromInputValue::from_input_value(&input).unwrap(); - assert_eq!( - output, - Input { - regular_field: "a".into(), - c: 55, - other: Some(true), - }, - ); - - // Test disable renaming - - let input: InputValue = ::serde_json::from_value(serde_json::json!({ - "regular_field": "hello", - })) - .unwrap(); - - let output: NoRenameInput = FromInputValue::from_input_value(&input).unwrap(); - assert_eq!( - output, - NoRenameInput { - regular_field: "hello".into(), - }, - ); -} - -#[test] -fn test_doc_comment() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = DocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("Object comment.")); -} - -#[test] -fn test_multi_doc_comment() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = MultiDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4.")); -} - -#[test] -fn test_doc_comment_override() { - let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); - let meta = OverrideDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("obj override")); -} diff --git a/tests/integration/src/codegen/mod.rs b/tests/integration/src/codegen/mod.rs deleted file mode 100644 index 3a709c66..00000000 --- a/tests/integration/src/codegen/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod derive_enum; -mod derive_input_object; -mod derive_object_with_raw_idents; -mod interface_attr_struct; -mod interface_attr_trait; -mod interface_derive; -mod object_attr; -mod object_derive; -mod scalar_attr_derive_input; -mod scalar_attr_type_alias; -mod scalar_derive; -mod scalar_value_derive; -mod subscription_attr; -mod union_attr; -mod union_derive; diff --git a/tests/integration/src/infallible_as_field_error.rs b/tests/integration/src/infallible_as_field_error.rs deleted file mode 100644 index 6a4539ff..00000000 --- a/tests/integration/src/infallible_as_field_error.rs +++ /dev/null @@ -1,8 +0,0 @@ -struct Query; - -#[juniper::graphql_object] -impl Query { - fn ping() -> Result { - Ok(false) - } -} diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs deleted file mode 100644 index 98019c14..00000000 --- a/tests/integration/src/lib.rs +++ /dev/null @@ -1,101 +0,0 @@ -#![deny(rust_2018_idioms)] - -#[cfg(test)] -mod arc_fields; -#[cfg(test)] -mod array; -#[cfg(test)] -mod codegen; -#[cfg(test)] -mod custom_scalar; -#[cfg(test)] -mod explicit_null; -#[cfg(test)] -mod infallible_as_field_error; -#[cfg(test)] -mod inside_macro; -#[cfg(test)] -mod issue_371; -#[cfg(test)] -mod issue_372; -#[cfg(test)] -mod issue_398; -#[cfg(test)] -mod issue_407; -#[cfg(test)] -mod issue_500; -#[cfg(test)] -mod issue_798; -#[cfg(test)] -mod issue_914; -#[cfg(test)] -mod issue_922; -#[cfg(test)] -mod issue_925; -#[cfg(test)] -mod issue_945; -#[cfg(test)] -mod pre_parse; - -#[cfg(test)] -/// Common utilities used across tests. -pub(crate) mod util { - use futures::StreamExt as _; - use juniper::{ - graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError, - GraphQLError, GraphQLType, RootNode, ScalarValue, Value, ValuesStream, - }; - - pub(crate) fn schema<'q, C, Q>( - query_root: Q, - ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> - where - Q: GraphQLType + 'q, - { - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) - } - - pub(crate) fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, - ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> - where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, - { - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) - } - - /// Extracts a single next value from the result returned by - /// [`juniper::resolve_into_stream()`] and transforms it into a regular - /// [`Value`]. - pub(crate) async fn extract_next<'a, S: ScalarValue>( - input: Result<(Value>, Vec>), GraphQLError<'a>>, - ) -> Result<(Value, Vec>), GraphQLError<'a>> { - let (stream, errs) = input?; - if !errs.is_empty() { - return Ok((Value::Null, errs)); - } - - if let Value::Object(obj) = stream { - for (name, mut val) in obj { - if let Value::Scalar(ref mut stream) = val { - return match stream.next().await { - Some(Ok(val)) => Ok((graphql_value!({ name: val }), vec![])), - Some(Err(e)) => Ok((Value::Null, vec![e])), - None => Ok((Value::Null, vec![])), - }; - } - } - } - - panic!("Expected to get Value::Object containing a Stream") - } -} diff --git a/tests/integration/src/arc_fields.rs b/tests/integration/tests/arc_fields.rs similarity index 61% rename from tests/integration/src/arc_fields.rs rename to tests/integration/tests/arc_fields.rs index 5f7799f9..75e97921 100644 --- a/tests/integration/src/arc_fields.rs +++ b/tests/integration/tests/arc_fields.rs @@ -1,15 +1,17 @@ use std::sync::Arc; +use juniper::{graphql_object, GraphQLInputObject}; + struct Query; -#[juniper::graphql_object] +#[graphql_object] impl Query { fn ping() -> Arc { Arc::new(false) } } -#[derive(juniper::GraphQLInputObject)] +#[derive(GraphQLInputObject)] struct Ping { expect_result: Arc, } diff --git a/tests/integration/src/array.rs b/tests/integration/tests/array.rs similarity index 100% rename from tests/integration/src/array.rs rename to tests/integration/tests/array.rs diff --git a/tests/integration/src/codegen/derive_object_with_raw_idents.rs b/tests/integration/tests/codegen_derive_object_with_raw_idents.rs similarity index 98% rename from tests/integration/src/codegen/derive_object_with_raw_idents.rs rename to tests/integration/tests/codegen_derive_object_with_raw_idents.rs index c1c5dea7..48d6d159 100644 --- a/tests/integration/src/codegen/derive_object_with_raw_idents.rs +++ b/tests/integration/tests/codegen_derive_object_with_raw_idents.rs @@ -99,6 +99,6 @@ async fn run_type_info_query(doc: &str) -> Value { assert_eq!(errs, []); - println!("Result: {:#?}", result); + println!("Result: {result:#?}"); result } diff --git a/tests/integration/tests/codegen_enum_derive.rs b/tests/integration/tests/codegen_enum_derive.rs new file mode 100644 index 00000000..295a2ead --- /dev/null +++ b/tests/integration/tests/codegen_enum_derive.rs @@ -0,0 +1,933 @@ +//! Tests for `#[derive(GraphQLEnum)]` macro. + +pub mod common; + +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, + DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, ScalarValue, +}; + +use self::common::util::{schema, schema_with_scalar}; + +mod trivial { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_enum() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "ENUM"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + {"name": "DROID", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod ignored_variant { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + #[allow(dead_code)] + #[graphql(ignore)] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + + fn droid() -> Character { + Character::Droid + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn err_on_droid() { + const DOC: &str = r#"{ + droid + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!(null), + vec![ExecutionError::new( + SourcePosition::new(14, 1, 12), + &["droid"], + FieldError::from("Cannot resolve ignored enum variant"), + )], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod ignored_generic_variant { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + Droid, + #[allow(dead_code)] + #[graphql(ignore)] + Ignored(T), + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character<()>) -> Character<()> { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + {"name": "DROID", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + Human, + + /// Droid doc. + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_enum() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "ENUM"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": false, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": false, + "deprecationReason": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + #![allow(deprecated)] + + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + #[deprecated] + Human, + + /// Droid doc. + #[deprecated(note = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": true, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod deprecation_from_graphql_attr { + #![allow(deprecated)] + + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + #[graphql(deprecated)] + Human, + + /// Droid doc. + #[graphql(deprecated = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": true, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + #![allow(deprecated)] + + use super::*; + + /// Doc comment. + #[derive(GraphQLEnum)] + #[graphql(name = "MyCharacter", desc = "Character doc.")] + enum Character { + /// Human doc. + #[graphql(name = "MY_HUMAN", desc = "My human doc.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + Human, + + /// Droid doc. + #[graphql(deprecated = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "MY_HUMAN", + "description": "My human doc.", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod renamed_all_fields { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(rename_all = "none")] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: Human) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "Human"}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "Human", "description": null}, + {"name": "Droid", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = DefaultScalarValue)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod custom_scalar { + use crate::common::MyScalarValue; + + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = MyScalarValue)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod explicit_generic_scalar { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = S)] + enum Character { + Human, + Droid, + #[allow(dead_code)] + #[graphql(ignore)] + Scalar(S), + } + + struct QueryRoot; + + #[graphql_object(scalar = S: ScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = S: ScalarValue + Clone)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod explicit_custom_context { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + #[derive(GraphQLEnum)] + #[graphql(context = CustomContext)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn pass_as_is(character: Character, _ctx: &CustomContext) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext("ctx".into()); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} diff --git a/tests/integration/tests/codegen_input_object_derive.rs b/tests/integration/tests/codegen_input_object_derive.rs new file mode 100644 index 00000000..d3113644 --- /dev/null +++ b/tests/integration/tests/codegen_input_object_derive.rs @@ -0,0 +1,887 @@ +//! Tests for `#[derive(GraphQLInputObject)]` macro. + +pub mod common; + +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, GraphQLError, + GraphQLInputObject, RuleError, +}; + +use self::common::util::schema; + +mod trivial { + use super::*; + + #[derive(GraphQLInputObject)] + struct Point2D { + x: f64, + y: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + point.x + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + x(point: { x: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + { + "name": "y", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod default_value { + use super::*; + + #[derive(GraphQLInputObject)] + struct Point2D { + #[graphql(default = 10.0)] + x: f64, + #[graphql(default = 10.0)] + y: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + point.x + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"query q($ve_num: Float!) { + literal_implicit_other_number: x(point: { y: 20 }) + literal_explicit_number: x(point: { x: 20 }) + literal_implicit_all: x(point: {}) + variable_explicit_number: x(point: { x: $ve_num }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {"ve_num": 40}, &()).await, + Ok(( + graphql_value!({ + "literal_implicit_other_number": 10.0, + "literal_explicit_number": 20.0, + "literal_implicit_all": 10.0, + "variable_explicit_number": 40.0, + }), + vec![], + )), + ); + } + + #[tokio::test] + async fn errs_on_explicit_null_literal() { + const DOC: &str = r#"{ x(point: { x: 20, y: null }) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Err(GraphQLError::ValidationError(vec![RuleError::new( + "Invalid value for argument \"point\", expected type \"Point2D!\"", + &[SourcePosition::new(11, 0, 11)], + )])) + ); + } + + #[tokio::test] + async fn errs_on_missing_variable() { + const DOC: &str = r#"query q($x: Float!){ x(point: { x: $x }) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Err(GraphQLError::ValidationError(vec![RuleError::new( + "Variable \"$x\" of required type \"Float!\" was not provided.", + &[SourcePosition::new(8, 0, 8)], + )])) + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [{ + "name": "x", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": "10", + }, { + "name": "y", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": "10", + }]}}), + vec![], + )), + ); + } +} + +mod default_nullable_value { + use super::*; + + #[derive(GraphQLInputObject)] + struct Point2D { + #[graphql(default = 10.0)] + x: Option, + #[graphql(default = 10.0)] + y: Option, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> Option { + point.x + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"query q( + $ve_num: Float, + $ve_null: Float, + $vi: Float, + $vde_num: Float = 40, + $vde_null: Float = 50, + $vdi: Float = 60, + ) { + literal_implicit_other_number: x(point: { y: 20 }) + literal_explicit_number: x(point: { x: 20 }) + literal_implicit_all: x(point: {}) + literal_explicit_null: x(point: { x: null }) + literal_implicit_other_null: x(point: { y: null }) + variable_explicit_number: x(point: { x: $ve_num }) + variable_explicit_null: x(point: { x: $ve_null }) + variable_implicit: x(point: { x: $vi }) + variable_default_explicit_number: x(point: { x: $vde_num }) + variable_default_explicit_null: x(point: { x: $vde_null }) + variable_default_implicit: x(point: { x: $vdi }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute( + DOC, + None, + &schema, + &graphql_vars! { + "ve_num": 30.0, + "ve_null": null, + "vde_num": 100, + "vde_null": null, + }, + &(), + ) + .await, + Ok(( + graphql_value!({ + "literal_implicit_other_number": 10.0, + "literal_explicit_number": 20.0, + "literal_implicit_all": 10.0, + "literal_explicit_null": null, + "literal_implicit_other_null": 10.0, + "variable_explicit_number": 30.0, + "variable_explicit_null": null, + "variable_implicit": 10.0, + "variable_default_explicit_number": 100.0, + "variable_default_explicit_null": null, + "variable_default_implicit": 60.0, + }), + vec![], + )), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + name + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x", + "description": null, + "type": {"name": "Float", "ofType": null}, + "defaultValue": "10", + }, + { + "name": "y", + "description": null, + "type": {"name": "Float", "ofType": null}, + "defaultValue": "10", + }, + ]}}), + vec![], + )), + ); + } +} + +mod ignored_field { + use super::*; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum System { + Cartesian, + } + + #[derive(GraphQLInputObject)] + struct Point2D { + x: f64, + y: f64, + #[graphql(ignore)] + shift: f64, + #[graphql(skip, default = System::Cartesian)] + system: System, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + assert_eq!(point.shift, f64::default()); + assert_eq!(point.system, System::Cartesian); + point.x + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + x(point: { x: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + { + "name": "y", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Point in a Cartesian system. + #[derive(GraphQLInputObject)] + struct Point2D { + /// Abscissa value. + x: f64, + + /// Ordinate value. + y_coord: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + point.x + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + x(point: { x: 10, yCoord: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Point2D"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Point in a Cartesian system.", + }}), + vec![] + )), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x", + "description": "Abscissa value.", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + { + "name": "yCoord", + "description": "Ordinate value.", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod description_from_graphql_attr { + use super::*; + + /// Ignored doc. + #[derive(GraphQLInputObject)] + #[graphql(name = "Point", desc = "Point in a Cartesian system.")] + struct Point2D { + /// Ignored doc. + #[graphql(name = "x", description = "Abscissa value.")] + x_coord: f64, + + /// Ordinate value. + y: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + point.x_coord + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + x(point: { x: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Point") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Point"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Point") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Point in a Cartesian system.", + }}), + vec![] + )), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x", + "description": "Abscissa value.", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + { + "name": "y", + "description": "Ordinate value.", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod renamed_all_fields { + use super::*; + + #[derive(GraphQLInputObject)] + #[graphql(rename_all = "none")] + struct Point2D { + x_coord: f64, + y: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point2D) -> f64 { + point.x_coord + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + x(point: { x_coord: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + const DOC: &str = r#"{ + __type(name: "Point2D") { + inputFields { + name + description + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + { + "name": "x_coord", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + { + "name": "y", + "description": null, + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, + ]}}), + vec![], + )), + ); + } +} diff --git a/tests/integration/src/codegen/interface_attr_struct.rs b/tests/integration/tests/codegen_interface_attr_struct.rs similarity index 77% rename from tests/integration/src/codegen/interface_attr_struct.rs rename to tests/integration/tests/codegen_interface_attr_struct.rs index 388bae78..b7b41178 100644 --- a/tests/integration/src/codegen/interface_attr_struct.rs +++ b/tests/integration/tests/codegen_interface_attr_struct.rs @@ -1,13 +1,15 @@ //! Tests for `#[graphql_interface]` macro placed on a struct. +pub mod common; + use std::marker::PhantomData; use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, - FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, + FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID, }; -use crate::util::{schema, schema_with_scalar}; +use self::common::util::{schema, schema_with_scalar}; mod no_implers { use super::*; @@ -117,13 +119,13 @@ mod trivial { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -188,13 +190,12 @@ mod trivial { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -247,17 +248,16 @@ mod trivial { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -347,13 +347,13 @@ mod explicit_alias { fn character(&self) -> CharacterEnum { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -418,13 +418,12 @@ mod explicit_alias { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -523,13 +522,13 @@ mod trivial_async { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -591,13 +590,12 @@ mod trivial_async { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -650,17 +648,16 @@ mod trivial_async { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -758,13 +755,13 @@ mod fallible_field { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -829,13 +826,12 @@ mod fallible_field { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -928,13 +924,13 @@ mod generic { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -999,13 +995,12 @@ mod generic { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1054,8 +1049,8 @@ mod description_from_doc_comment { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1121,7 +1116,7 @@ mod deprecation_from_attr { } fn b() -> String { - "b".to_owned() + "b".into() } } @@ -1131,8 +1126,8 @@ mod deprecation_from_attr { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1258,7 +1253,7 @@ mod explicit_name_description_and_deprecation { } fn a() -> String { - "a".to_owned() + "a".into() } fn b() -> &'static str { @@ -1272,8 +1267,8 @@ mod explicit_name_description_and_deprecation { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1532,13 +1527,13 @@ mod explicit_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1603,13 +1598,12 @@ mod explicit_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1619,7 +1613,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -1662,13 +1656,13 @@ mod custom_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1733,13 +1727,12 @@ mod custom_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema_with_scalar::(*root); + let schema = schema_with_scalar::(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1790,13 +1783,13 @@ mod explicit_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1861,13 +1854,12 @@ mod explicit_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1918,13 +1910,13 @@ mod bounded_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1989,13 +1981,12 @@ mod bounded_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2031,8 +2022,8 @@ mod ignored_method { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -2143,13 +2134,13 @@ mod field_return_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2214,13 +2205,12 @@ mod field_return_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2297,14 +2287,14 @@ mod field_return_union_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), key_feature: Knowledge { value: 10 }, } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), strength: 42, } .into(), @@ -2386,14 +2376,12 @@ mod field_return_union_subtyping { } }"#; - for (root, expected_id, expected_val) in &[ + for (root, expected_id, expected_val) in [ (QueryRoot::Human, "human-32", 10), (QueryRoot::Droid, "droid-99", 42), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; - let expected_val = *expected_val; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -2433,7 +2421,7 @@ mod nullable_argument_subtyping { fn id(&self, is_present: Option) -> &str { is_present .unwrap_or_default() - .then(|| self.id.as_str()) + .then_some(&*self.id) .unwrap_or("missing") } @@ -2453,13 +2441,13 @@ mod nullable_argument_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2524,13 +2512,12 @@ mod nullable_argument_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "missing"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2539,6 +2526,533 @@ mod nullable_argument_subtyping { } } +mod simple_subtyping { + use super::*; + + #[graphql_interface(for = [ResourceValue, Endpoint])] + struct Node { + id: Option, + } + + #[graphql_interface(impl = NodeValue, for = Endpoint)] + struct Resource { + id: ID, + url: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::new("1"), + url: "2".into(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::new("3"), + url: "4".into(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_subtyping { + use super::*; + + #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] + struct Node { + id: ID, + } + + #[graphql_interface(for = [HumanConnection, DroidConnection])] + struct Connection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = Luke)] + struct Human { + id: ID, + home_planet: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = R2D2)] + struct Droid { + id: ID, + primary_function: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".into(), + father: "SPOILER".into(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".into(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} + mod preserves_visibility { use super::*; diff --git a/tests/integration/src/codegen/interface_attr_trait.rs b/tests/integration/tests/codegen_interface_attr_trait.rs similarity index 80% rename from tests/integration/src/codegen/interface_attr_trait.rs rename to tests/integration/tests/codegen_interface_attr_trait.rs index a81a758b..1556e6a9 100644 --- a/tests/integration/src/codegen/interface_attr_trait.rs +++ b/tests/integration/tests/codegen_interface_attr_trait.rs @@ -1,12 +1,14 @@ //! Tests for `#[graphql_interface]` macro placed on a trait. +pub mod common; + use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion, - IntoFieldError, ScalarValue, + IntoFieldError, ScalarValue, ID, }; -use crate::util::{schema, schema_with_scalar}; +use self::common::util::{schema, schema_with_scalar}; mod no_implers { use super::*; @@ -116,13 +118,13 @@ mod trivial { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -187,13 +189,12 @@ mod trivial { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -246,17 +247,16 @@ mod trivial { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -346,13 +346,13 @@ mod explicit_alias { fn character(&self) -> CharacterEnum { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -417,13 +417,12 @@ mod explicit_alias { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -522,13 +521,13 @@ mod trivial_async { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -593,13 +592,12 @@ mod trivial_async { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -652,17 +650,16 @@ mod trivial_async { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -760,13 +757,13 @@ mod fallible_field { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -831,13 +828,12 @@ mod fallible_field { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -927,13 +923,13 @@ mod generic { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -995,13 +991,12 @@ mod generic { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1074,8 +1069,8 @@ mod argument { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1085,7 +1080,7 @@ mod argument { async fn resolves_id_field() { let schema = schema(QueryRoot); - for (input, expected) in &[ + for (input, expected) in [ ( "{ character { idWide(isNumber: true), idWide2(isNumber: true) } }", "human-32", @@ -1095,10 +1090,8 @@ mod argument { "none", ), ] { - let expected: &str = *expected; - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, + execute(input, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"character": { "idWide": expected, @@ -1211,7 +1204,7 @@ mod default_argument { fn id( &self, #[graphql(default)] first: String, - #[graphql(default = "second".to_string())] second: String, + #[graphql(default = "second")] second: String, #[graphql(default = "t")] third: String, ) -> String; @@ -1227,7 +1220,7 @@ mod default_argument { } async fn id(&self, first: String, second: String, third: String) -> String { - format!("{}|{}&{}", first, second, third) + format!("{first}|{second}&{third}") } } @@ -1244,7 +1237,7 @@ mod default_argument { async fn resolves_id_field() { let schema = schema(QueryRoot); - for (input, expected) in &[ + for (input, expected) in [ ("{ character { id } }", "|second&t"), (r#"{ character { id(first: "first") } }"#, "first|second&t"), (r#"{ character { id(second: "") } }"#, "|&t"), @@ -1257,10 +1250,8 @@ mod default_argument { "first|&", ), ] { - let expected: &str = *expected; - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, + execute(input, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected}}), vec![])), ); } @@ -1270,14 +1261,12 @@ mod default_argument { async fn resolves_info_field() { let schema = schema(QueryRoot); - for (input, expected) in &[ + for (input, expected) in [ ("{ character { info } }", 1), ("{ character { info(coord: {x: 2}) } }", 2), ] { - let expected: i32 = *expected; - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, + execute(input, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"info": expected}}), vec![])), ); } @@ -1311,21 +1300,21 @@ mod default_argument { "args": [{ "name": "first", "defaultValue": r#""""#, - "type": {"name": "String", "ofType": null}, + "type": {"name": null, "ofType": {"name": "String"}}, }, { "name": "second", "defaultValue": r#""second""#, - "type": {"name": "String", "ofType": null}, + "type": {"name": null, "ofType": {"name": "String"}}, }, { "name": "third", "defaultValue": r#""t""#, - "type": {"name": "String", "ofType": null}, + "type": {"name": null, "ofType": {"name": "String"}}, }], }, { "args": [{ "name": "coord", "defaultValue": "{x: 1}", - "type": {"name": "Point", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Point"}}, }], }]}}), vec![], @@ -1358,8 +1347,8 @@ mod description_from_doc_comment { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1425,7 +1414,7 @@ mod deprecation_from_attr { } fn b() -> String { - "b".to_owned() + "b".into() } } @@ -1435,8 +1424,8 @@ mod deprecation_from_attr { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1562,7 +1551,7 @@ mod explicit_name_description_and_deprecation { } fn a() -> String { - "a".to_owned() + "a".into() } fn b() -> &'static str { @@ -1576,8 +1565,8 @@ mod explicit_name_description_and_deprecation { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1854,13 +1843,13 @@ mod explicit_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1925,13 +1914,12 @@ mod explicit_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1941,7 +1929,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -1984,13 +1972,13 @@ mod custom_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2055,13 +2043,12 @@ mod custom_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema_with_scalar::(*root); + let schema = schema_with_scalar::(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2112,13 +2099,13 @@ mod explicit_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2183,13 +2170,12 @@ mod explicit_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2240,13 +2226,13 @@ mod bounded_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2311,13 +2297,12 @@ mod bounded_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2401,13 +2386,13 @@ mod explicit_custom_context { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2474,15 +2459,12 @@ mod explicit_custom_context { } }"#; - for (root, expected_id, expected_info, expexted_more) in &[ + for (root, expected_id, expected_info, expexted_more) in [ (QueryRoot::Human, "human-32", "earth", "human"), (QueryRoot::Droid, "droid-99", "run", "droid"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - let expexted_more: &str = *expexted_more; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( @@ -2561,11 +2543,11 @@ mod inferred_custom_context_from_field { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - home_planet: "earth".to_string(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - primary_function: "run".to_string(), + primary_function: "run".into(), } .into(), } @@ -2633,15 +2615,13 @@ mod inferred_custom_context_from_field { } }"#; - for (root, expected_id, expected_info) in &[ + for (root, expected_id, expected_info) in [ (QueryRoot::Human, "human-ctx", "earth"), (QueryRoot::Droid, "droid-ctx", "run"), ] { - let schema = schema(*root); - let ctx = CustomContext(expected_id.to_string()); + let schema = schema(root); + let ctx = CustomContext(expected_id.into()); - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( @@ -2725,11 +2705,11 @@ mod executor { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - home_planet: "earth".to_string(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - primary_function: "run".to_string(), + primary_function: "run".into(), } .into(), } @@ -2795,10 +2775,9 @@ mod executor { } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); + for (root, expected_info) in [(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(root); - let expected_info: &str = *expected_info; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -2869,8 +2848,8 @@ mod ignored_method { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -2981,13 +2960,13 @@ mod field_return_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -3052,13 +3031,12 @@ mod field_return_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -3136,14 +3114,14 @@ mod field_return_union_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), key_feature: Knowledge { value: 10 }, } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), strength: 42, } .into(), @@ -3225,14 +3203,12 @@ mod field_return_union_subtyping { } }"#; - for (root, expected_id, expected_val) in &[ + for (root, expected_id, expected_val) in [ (QueryRoot::Human, "human-32", 10), (QueryRoot::Droid, "droid-99", 42), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; - let expected_val = *expected_val; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -3272,7 +3248,7 @@ mod nullable_argument_subtyping { fn id(&self, is_present: Option) -> &str { is_present .unwrap_or_default() - .then(|| self.id.as_str()) + .then_some(&*self.id) .unwrap_or("missing") } @@ -3292,13 +3268,13 @@ mod nullable_argument_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -3363,13 +3339,12 @@ mod nullable_argument_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "missing"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -3378,6 +3353,533 @@ mod nullable_argument_subtyping { } } +mod simple_subtyping { + use super::*; + + #[graphql_interface(for = [ResourceValue, Endpoint])] + trait Node { + fn id() -> Option; + } + + #[graphql_interface(impl = NodeValue, for = Endpoint)] + trait Resource { + fn id(&self) -> &ID; + fn url(&self) -> Option<&str>; + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::new("1"), + url: "2".into(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::new("3"), + url: "4".into(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_subtyping { + use super::*; + + #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] + trait Node { + fn id() -> ID; + } + + #[graphql_interface(for = [HumanConnection, DroidConnection])] + trait Connection { + fn nodes(&self) -> &[NodeValue]; + } + + #[graphql_interface(impl = NodeValue, for = Luke)] + trait Human { + fn id(&self) -> &ID; + fn home_planet(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = R2D2)] + trait Droid { + fn id() -> ID; + fn primary_function() -> String; + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".into(), + father: "SPOILER".into(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".into(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} + mod preserves_visibility { use super::*; diff --git a/tests/integration/src/codegen/interface_derive.rs b/tests/integration/tests/codegen_interface_derive.rs similarity index 77% rename from tests/integration/src/codegen/interface_derive.rs rename to tests/integration/tests/codegen_interface_derive.rs index 7882f717..6eb5f0f4 100644 --- a/tests/integration/src/codegen/interface_derive.rs +++ b/tests/integration/tests/codegen_interface_derive.rs @@ -1,13 +1,15 @@ //! Tests for `#[derive(GraphQLInterface)]` macro. +pub mod common; + use std::marker::PhantomData; use juniper::{ execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError, - FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, + FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID, }; -use crate::util::{schema, schema_with_scalar}; +use self::common::util::{schema, schema_with_scalar}; mod no_implers { use super::*; @@ -118,13 +120,13 @@ mod trivial { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -189,13 +191,12 @@ mod trivial { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -248,17 +249,16 @@ mod trivial { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -349,13 +349,13 @@ mod explicit_alias { fn character(&self) -> CharacterEnum { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -420,13 +420,12 @@ mod explicit_alias { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -526,13 +525,13 @@ mod trivial_async { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -597,13 +596,12 @@ mod trivial_async { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -656,17 +654,16 @@ mod trivial_async { async fn registers_itself_in_implementers() { let schema = schema(QueryRoot::Human); - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ interfaces {{ kind name }} }} }}"#, - object, ); assert_eq!( @@ -765,13 +762,13 @@ mod fallible_field { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -836,13 +833,12 @@ mod fallible_field { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -936,13 +932,13 @@ mod generic { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1007,13 +1003,12 @@ mod generic { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1063,8 +1058,8 @@ mod description_from_doc_comment { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1131,7 +1126,7 @@ mod deprecation_from_attr { } fn b() -> String { - "b".to_owned() + "b".into() } } @@ -1141,8 +1136,8 @@ mod deprecation_from_attr { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1269,7 +1264,7 @@ mod explicit_name_description_and_deprecation { } fn a() -> String { - "a".to_owned() + "a".into() } fn b() -> &'static str { @@ -1283,8 +1278,8 @@ mod explicit_name_description_and_deprecation { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -1545,13 +1540,13 @@ mod explicit_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1616,13 +1611,12 @@ mod explicit_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1632,7 +1626,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -1676,13 +1670,13 @@ mod custom_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1747,13 +1741,12 @@ mod custom_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema_with_scalar::(*root); + let schema = schema_with_scalar::(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1805,13 +1798,13 @@ mod explicit_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -1876,13 +1869,12 @@ mod explicit_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -1934,13 +1926,13 @@ mod bounded_generic_scalar { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2005,13 +1997,12 @@ mod bounded_generic_scalar { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2048,8 +2039,8 @@ mod ignored_method { impl QueryRoot { fn character(&self) -> CharacterValue { Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into() } @@ -2161,13 +2152,13 @@ mod field_return_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2232,13 +2223,12 @@ mod field_return_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2316,14 +2306,14 @@ mod field_return_union_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), key_feature: Knowledge { value: 10 }, } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), strength: 42, } .into(), @@ -2405,14 +2395,12 @@ mod field_return_union_subtyping { } }"#; - for (root, expected_id, expected_val) in &[ + for (root, expected_id, expected_val) in [ (QueryRoot::Human, "human-32", 10), (QueryRoot::Droid, "droid-99", 42), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; - let expected_val = *expected_val; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -2453,7 +2441,7 @@ mod nullable_argument_subtyping { fn id(&self, is_present: Option) -> &str { is_present .unwrap_or_default() - .then(|| self.id.as_str()) + .then_some(&*self.id) .unwrap_or("missing") } @@ -2473,13 +2461,13 @@ mod nullable_argument_subtyping { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), } .into(), } @@ -2544,13 +2532,12 @@ mod nullable_argument_subtyping { } }"#; - for (root, expected_id) in &[ + for (root, expected_id) in [ (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "missing"), ] { - let schema = schema(*root); + let schema = schema(root); - let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), @@ -2559,6 +2546,539 @@ mod nullable_argument_subtyping { } } +mod simple_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [ResourceValue, Endpoint])] + struct Node { + id: Option, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = Endpoint)] + struct Resource { + id: ID, + url: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::new("1"), + url: "2".into(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::new("3"), + url: "4".into(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [HumanValue, DroidValue, Luke, R2D2])] + struct Node { + id: ID, + } + + #[derive(GraphQLInterface)] + #[graphql(for = [HumanConnection, DroidConnection])] + struct Connection { + nodes: Vec, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = Luke)] + struct Human { + id: ID, + home_planet: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = R2D2)] + struct Droid { + id: ID, + primary_function: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".into(), + father: "SPOILER".into(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".into(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{name}") {{ + kind + }} + }}"#, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} + mod preserves_visibility { use super::*; diff --git a/tests/integration/src/codegen/object_attr.rs b/tests/integration/tests/codegen_object_attr.rs similarity index 94% rename from tests/integration/src/codegen/object_attr.rs rename to tests/integration/tests/codegen_object_attr.rs index 9161cc55..dc813e4c 100644 --- a/tests/integration/src/codegen/object_attr.rs +++ b/tests/integration/tests/codegen_object_attr.rs @@ -1,35 +1,13 @@ //! Tests for `#[graphql_object]` macro. +pub mod common; + use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, - EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, - GraphQLType, IntoFieldError, RootNode, ScalarValue, + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, Executor, FieldError, + FieldResult, GraphQLInputObject, GraphQLObject, IntoFieldError, ScalarValue, }; -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} +use self::common::util::{schema, schema_with_scalar}; mod trivial { use super::*; @@ -376,7 +354,7 @@ mod fallible_method { impl QueryRoot { fn human() -> Human { Human { - id: "human-32".to_string(), + id: "human-32".into(), } } } @@ -885,22 +863,20 @@ mod nested_generic_lifetime_async { #[tokio::test] async fn uses_type_name_without_type_params() { - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ name }} }}"#, - object, ); let schema = schema(QueryRoot("mars".into())); - let expected_name: &str = *object; assert_eq!( execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + Ok((graphql_value!({"__type": {"name": object}}), vec![])), ); } } @@ -918,7 +894,7 @@ mod argument { } async fn home_planet(&self, r#raw_arg: String, r#async: Option) -> String { - format!("{},{:?}", r#raw_arg, r#async) + format!("{raw_arg},{async:?}") } } @@ -1045,10 +1021,10 @@ mod default_argument { impl Human { fn id( #[graphql(default)] arg1: i32, - #[graphql(default = "second".to_string())] arg2: String, + #[graphql(default = "second".to_string())] arg2: Option, #[graphql(default = true)] r#arg3: bool, ) -> String { - format!("{}|{}&{}", arg1, arg2, r#arg3) + format!("{arg1}|{arg2:?}&{arg3}") } fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> i32 { @@ -1069,20 +1045,70 @@ mod default_argument { async fn resolves_id_field() { let schema = schema(QueryRoot); - for (input, expected) in &[ - ("{ human { id } }", "0|second&true"), - ("{ human { id(arg1: 1) } }", "1|second&true"), - (r#"{ human { id(arg2: "") } }"#, "0|&true"), - (r#"{ human { id(arg1: 2, arg2: "") } }"#, "2|&true"), + for (input, expected, vars) in [ + ( + "{ human { id } }", + r#"0|Some("second")&true"#, + graphql_vars! {}, + ), + ( + "{ human { id(arg1: 1) } }", + r#"1|Some("second")&true"#, + graphql_vars! {}, + ), + ( + r#"{ human { id(arg2: "other") } }"#, + r#"0|Some("other")&true"#, + graphql_vars! {}, + ), + ( + "{ human { id(arg2: null) } }", + r#"0|None&true"#, + graphql_vars! {}, + ), + ( + "query q($arg2: String) { human { id(arg2: $arg2) } }", + r#"0|Some("second")&true"#, + graphql_vars! {}, + ), + ( + "query q($arg2: String) { human{ id(arg2: $arg2) } }", + r#"0|None&true"#, + graphql_vars! { "arg2": null }, + ), + ( + "query q($arg2: String) { human{ id(arg2: $arg2) } }", + r#"0|Some("other")&true"#, + graphql_vars! { "arg2": "other" }, + ), + ( + r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#, + r#"0|Some("other")&true"#, + graphql_vars! {}, + ), + ( + r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#, + r#"0|None&true"#, + graphql_vars! { "arg2": null }, + ), + ( + r#"query q($arg2: String = "other") { human { id(arg2: $arg2) } }"#, + r#"0|Some("hello")&true"#, + graphql_vars! { "arg2": "hello" }, + ), + ( + r#"{ human { id(arg1: 2, arg2: "") } }"#, + r#"2|Some("")&true"#, + graphql_vars! {}, + ), ( r#"{ human { id(arg1: 1, arg2: "", arg3: false) } }"#, - "1|&false", + r#"1|Some("")&false"#, + graphql_vars! {}, ), ] { - let expected: &str = *expected; - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, + execute(input, None, &schema, &vars, &(),).await, Ok((graphql_value!({"human": {"id": expected}}), vec![])), ); } @@ -1092,14 +1118,12 @@ mod default_argument { async fn resolves_info_field() { let schema = schema(QueryRoot); - for (input, expected) in &[ + for (input, expected) in [ ("{ human { info } }", 1), ("{ human { info(coord: { x: 2 }) } }", 2), ] { - let expected: i32 = *expected; - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, + execute(input, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"human": {"info": expected}}), vec![])), ); } @@ -1133,7 +1157,7 @@ mod default_argument { "args": [{ "name": "arg1", "defaultValue": "0", - "type": {"name": "Int", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Int"}}, }, { "name": "arg2", "defaultValue": r#""second""#, @@ -1141,13 +1165,13 @@ mod default_argument { }, { "name": "arg3", "defaultValue": "true", - "type": {"name": "Boolean", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Boolean"}}, }], }, { "args": [{ "name": "coord", "defaultValue": "{x: 1}", - "type": {"name": "Point", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Point"}}, }], }]}}), vec![], @@ -1621,7 +1645,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; diff --git a/tests/integration/src/codegen/object_derive.rs b/tests/integration/tests/codegen_object_derive.rs similarity index 95% rename from tests/integration/src/codegen/object_derive.rs rename to tests/integration/tests/codegen_object_derive.rs index 126c315a..b3ab2299 100644 --- a/tests/integration/src/codegen/object_derive.rs +++ b/tests/integration/tests/codegen_object_derive.rs @@ -1,34 +1,13 @@ //! Tests for `#[derive(GraphQLObject)]` macro. +pub mod common; + use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLObject, + ScalarValue, }; -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} +use self::common::util::{schema, schema_with_scalar}; mod trivial { use super::*; @@ -406,22 +385,20 @@ mod nested_generic_lifetime_async { #[tokio::test] async fn uses_type_name_without_type_params() { - for object in &["Human", "Droid"] { + for object in ["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{object}") {{ name }} }}"#, - object, ); let schema = schema(QueryRoot("mars".into())); - let expected_name: &str = *object; assert_eq!( execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + Ok((graphql_value!({"__type": {"name": object}}), vec![])), ); } } @@ -853,7 +830,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; diff --git a/tests/integration/src/codegen/scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs similarity index 98% rename from tests/integration/src/codegen/scalar_attr_derive_input.rs rename to tests/integration/tests/codegen_scalar_attr_derive_input.rs index ccfa3575..b691aea5 100644 --- a/tests/integration/src/codegen/scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -2,6 +2,8 @@ //! //! [`DeriveInput`]: syn::DeriveInput +pub mod common; + use std::fmt; use chrono::{DateTime, TimeZone, Utc}; @@ -10,9 +12,9 @@ use juniper::{ ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -use crate::{ - custom_scalar::MyScalarValue, +use self::common::{ util::{schema, schema_with_scalar}, + MyScalarValue, }; mod trivial { @@ -29,10 +31,10 @@ mod trivial { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { >::from_str(t) } } @@ -237,10 +239,10 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -312,10 +314,10 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } } @@ -388,7 +390,7 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -458,7 +460,7 @@ mod multiple_delegated_parse_token { impl StringOrInt { fn to_output(&self) -> Value { match self { - Self::String(str) => Value::scalar(str.to_owned()), + Self::String(s) => Value::scalar(s.to_owned()), Self::Int(i) => Value::scalar(*i), } } @@ -467,7 +469,7 @@ mod multiple_delegated_parse_token { v.as_string_value() .map(|s| Self::String(s.to_owned())) .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } } @@ -533,11 +535,11 @@ mod where_attribute { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } @@ -599,10 +601,10 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } } @@ -691,11 +693,11 @@ mod with_module { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } } @@ -759,7 +761,7 @@ mod description_from_doc_comment { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -835,7 +837,7 @@ mod description_from_attribute { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -911,7 +913,7 @@ mod custom_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -987,7 +989,7 @@ mod generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -1062,7 +1064,7 @@ mod bounded_generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } diff --git a/tests/integration/src/codegen/scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs similarity index 98% rename from tests/integration/src/codegen/scalar_attr_type_alias.rs rename to tests/integration/tests/codegen_scalar_attr_type_alias.rs index 9dfd8f6c..e6af88b3 100644 --- a/tests/integration/src/codegen/scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -1,5 +1,7 @@ //! Tests for `#[graphql_scalar]` macro placed on a type alias. +pub mod common; + use std::fmt; use chrono::{DateTime, TimeZone, Utc}; @@ -8,9 +10,9 @@ use juniper::{ ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -use crate::{ - custom_scalar::MyScalarValue, +use self::common::{ util::{schema, schema_with_scalar}, + MyScalarValue, }; mod all_custom_resolvers { @@ -34,10 +36,10 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -115,10 +117,10 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -152,11 +154,10 @@ mod explicit_name { for name in ["CustomCounter", "CustomScalar"] { let doc = format!( r#"{{ - __type(name: "{}") {{ + __type(name: "{name}") {{ kind }} }}"#, - name, ); let schema = schema(QueryRoot); @@ -216,7 +217,7 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } struct QueryRoot; @@ -290,7 +291,7 @@ mod multiple_delegated_parse_token { fn to_output(v: &StringOrInt) -> Value { match v { - StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::String(s) => Value::scalar(s.to_owned()), StringOrInt::Int(i) => Value::scalar(*i), } } @@ -299,7 +300,7 @@ mod multiple_delegated_parse_token { v.as_string_value() .map(|s| StringOrInt::String(s.to_owned())) .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } struct QueryRoot; @@ -366,11 +367,11 @@ mod where_attribute { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } @@ -434,10 +435,10 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } } @@ -528,11 +529,11 @@ mod with_module { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } } @@ -600,7 +601,7 @@ mod description_from_doc_comment { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -684,7 +685,7 @@ mod description_from_attribute { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -768,7 +769,7 @@ mod custom_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -852,7 +853,7 @@ mod generic_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -936,7 +937,7 @@ mod bounded_generic_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } diff --git a/tests/integration/src/codegen/scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs similarity index 98% rename from tests/integration/src/codegen/scalar_derive.rs rename to tests/integration/tests/codegen_scalar_derive.rs index a538095f..118fb010 100644 --- a/tests/integration/src/codegen/scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -1,5 +1,7 @@ //! Tests for `#[derive(GraphQLScalar)]` macro. +pub mod common; + use std::fmt; use chrono::{DateTime, TimeZone, Utc}; @@ -8,9 +10,9 @@ use juniper::{ ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -use crate::{ - custom_scalar::MyScalarValue, +use self::common::{ util::{schema, schema_with_scalar}, + MyScalarValue, }; mod trivial { @@ -27,10 +29,10 @@ mod trivial { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { >::from_str(t) } } @@ -235,10 +237,10 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -311,10 +313,10 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } } @@ -388,7 +390,7 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -459,7 +461,7 @@ mod multiple_delegated_parse_token { impl StringOrInt { fn to_output(&self) -> Value { match self { - Self::String(str) => Value::scalar(str.to_owned()), + Self::String(s) => Value::scalar(s.to_owned()), Self::Int(i) => Value::scalar(*i), } } @@ -468,7 +470,7 @@ mod multiple_delegated_parse_token { v.as_string_value() .map(|s| Self::String(s.to_owned())) .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } } @@ -535,11 +537,11 @@ mod where_attribute { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } @@ -602,10 +604,10 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } } @@ -695,11 +697,11 @@ mod with_module { Tz::Offset: fmt::Display, { v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) }) } } @@ -764,7 +766,7 @@ mod description_from_doc_comment { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -841,7 +843,7 @@ mod description_from_attribute { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -918,7 +920,7 @@ mod custom_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -995,7 +997,7 @@ mod generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) + .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } } @@ -1071,7 +1073,7 @@ mod bounded_generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) - .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {v}")) } } diff --git a/tests/integration/src/codegen/scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs similarity index 100% rename from tests/integration/src/codegen/scalar_value_derive.rs rename to tests/integration/tests/codegen_scalar_value_derive.rs diff --git a/tests/integration/src/codegen/subscription_attr.rs b/tests/integration/tests/codegen_subscription_attr.rs similarity index 96% rename from tests/integration/src/codegen/subscription_attr.rs rename to tests/integration/tests/codegen_subscription_attr.rs index 306b8c8e..09ffa957 100644 --- a/tests/integration/src/codegen/subscription_attr.rs +++ b/tests/integration/tests/codegen_subscription_attr.rs @@ -1,5 +1,7 @@ //! Tests for `#[graphql_subscription]` macro. +pub mod common; + use std::pin::Pin; use futures::{future, stream, FutureExt as _}; @@ -9,7 +11,7 @@ use juniper::{ GraphQLInputObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; -use crate::util::extract_next; +use self::common::util::extract_next; struct Query; @@ -53,12 +55,12 @@ mod trivial { #[graphql_subscription] impl Human { async fn id() -> Stream<'static, String> { - Box::pin(stream::once(future::ready("human-32".to_owned()))) + Box::pin(stream::once(future::ready("human-32".into()))) } // TODO: Make work for `Stream<'_, String>`. async fn home_planet(&self) -> Stream<'static, String> { - Box::pin(stream::once(future::ready("earth".to_owned()))) + Box::pin(stream::once(future::ready("earth".into()))) } } @@ -227,7 +229,7 @@ mod ignored_method { #[graphql_subscription] impl Human { async fn id() -> Stream<'static, String> { - Box::pin(stream::once(future::ready("human-32".to_owned()))) + Box::pin(stream::once(future::ready("human-32".into()))) } #[allow(dead_code)] @@ -291,7 +293,7 @@ mod fallible_method { #[graphql_subscription] impl Human { async fn id(&self) -> Result, CustomError> { - Ok(Box::pin(stream::once(future::ready("human-32".to_owned())))) + Ok(Box::pin(stream::once(future::ready("human-32".into())))) } async fn home_planet<__S>() -> FieldResult, __S> { @@ -387,10 +389,7 @@ mod argument { r#raw_arg: String, r#async: Option, ) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(format!( - "{},{:?}", - r#raw_arg, r#async - )))) + Box::pin(stream::once(future::ready(format!("{raw_arg},{async:?}")))) } } @@ -524,10 +523,7 @@ mod default_argument { #[graphql(default = "second".to_string())] arg2: String, #[graphql(default = true)] r#arg3: bool, ) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(format!( - "{}|{}&{}", - arg1, arg2, r#arg3 - )))) + Box::pin(stream::once(future::ready(format!("{arg1}|{arg2}&{arg3}")))) } async fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> Stream<'static, i32> { @@ -539,7 +535,7 @@ mod default_argument { async fn resolves_id_field() { let schema = schema(Query, Human); - for (input, expected) in &[ + for (input, expected) in [ ("subscription { id }", "0|second&true"), ("subscription { id(arg1: 1) }", "1|second&true"), (r#"subscription { id(arg2: "") }"#, "0|&true"), @@ -549,10 +545,8 @@ mod default_argument { "1|&false", ), ] { - let expected: &str = *expected; - assert_eq!( - resolve_into_stream(*input, None, &schema, &graphql_vars! {}, &()) + resolve_into_stream(input, None, &schema, &graphql_vars! {}, &()) .then(|s| extract_next(s)) .await, Ok((graphql_value!({ "id": expected }), vec![])), @@ -564,14 +558,12 @@ mod default_argument { async fn resolves_info_field() { let schema = schema(Query, Human); - for (input, expected) in &[ + for (input, expected) in [ ("subscription { info }", 1), ("subscription { info(coord: { x: 2 }) }", 2), ] { - let expected: i32 = *expected; - assert_eq!( - resolve_into_stream(*input, None, &schema, &graphql_vars! {}, &()) + resolve_into_stream(input, None, &schema, &graphql_vars! {}, &()) .then(|s| extract_next(s)) .await, Ok((graphql_value!({ "info": expected }), vec![])), @@ -607,21 +599,21 @@ mod default_argument { "args": [{ "name": "arg1", "defaultValue": "0", - "type": {"name": "Int", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Int"}}, }, { "name": "arg2", "defaultValue": r#""second""#, - "type": {"name": "String", "ofType": null}, + "type": {"name": null, "ofType": {"name": "String"}}, }, { "name": "arg3", "defaultValue": "true", - "type": {"name": "Boolean", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Boolean"}}, }], }, { "args": [{ "name": "coord", "defaultValue": "{x: 1}", - "type": {"name": "Point", "ofType": null}, + "type": {"name": null, "ofType": {"name": "Point"}}, }], }]}}), vec![], @@ -648,7 +640,7 @@ mod generic { #[graphql_subscription(name = "HumanString")] impl Human { async fn id(&self) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(self.id.to_owned()))) + Box::pin(stream::once(future::ready(self.id.clone()))) } } @@ -735,7 +727,7 @@ mod generic_lifetime { // TODO: Make it work with `Stream<'_, &str>`. async fn planet(&self) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(self.home_planet.to_owned()))) + Box::pin(stream::once(future::ready(self.home_planet.into()))) } } @@ -743,12 +735,12 @@ mod generic_lifetime { impl<'id, 'p> Human<'p, &'id str> { // TODO: Make it work with `Stream<'_, &str>`. async fn id(&self) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(self.id.to_owned()))) + Box::pin(stream::once(future::ready(self.id.into()))) } // TODO: Make it work with `Stream<'_, &str>`. async fn planet(&self) -> Stream<'static, String> { - Box::pin(stream::once(future::ready(self.home_planet.to_owned()))) + Box::pin(stream::once(future::ready(self.home_planet.into()))) } } @@ -1361,7 +1353,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -1702,7 +1694,7 @@ mod executor { S: ScalarValue, { Box::pin(stream::once(future::ready( - executor.look_ahead().field_name().to_owned(), + executor.look_ahead().field_name().into(), ))) } diff --git a/tests/integration/src/codegen/union_attr.rs b/tests/integration/tests/codegen_union_attr.rs similarity index 90% rename from tests/integration/src/codegen/union_attr.rs rename to tests/integration/tests/codegen_union_attr.rs index 9f032c4c..23085ef5 100644 --- a/tests/integration/src/codegen/union_attr.rs +++ b/tests/integration/tests/codegen_union_attr.rs @@ -1,10 +1,14 @@ //! Tests for `#[graphql_union]` macro. +pub mod common; + use juniper::{ execute, graphql_object, graphql_union, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, + GraphQLObject, ScalarValue, }; +use self::common::util::{schema, schema_with_scalar}; + #[derive(GraphQLObject)] struct Human { id: String, @@ -51,31 +55,6 @@ struct EwokCustomContext { funny: bool, } -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - mod trivial { use super::*; @@ -113,12 +92,12 @@ mod trivial { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -250,12 +229,12 @@ mod generic { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -343,8 +322,8 @@ mod description_from_doc_comment { impl QueryRoot { fn character(&self) -> Box> { Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -416,8 +395,8 @@ mod explicit_name_and_description { impl QueryRoot { fn character(&self) -> Box> { Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -517,12 +496,12 @@ mod explicit_scalar { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -570,7 +549,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -608,12 +587,12 @@ mod custom_scalar { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -697,12 +676,12 @@ mod explicit_generic_scalar { fn character<__S: ScalarValue>(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -786,12 +765,12 @@ mod bounded_generic_scalar { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; ch @@ -872,12 +851,12 @@ mod explicit_custom_context { fn character(&self, ctx: &CustomContext) -> Box> { let ch: Box> = match ctx { CustomContext::Human => Box::new(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Box::new(DroidCustomContext { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), _ => unimplemented!(), }; @@ -959,12 +938,12 @@ mod inferred_custom_context { fn character(&self, ctx: &CustomContext) -> Box> { let ch: Box> = match ctx { CustomContext::Human => Box::new(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Box::new(DroidCustomContext { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), _ => unimplemented!(), }; @@ -1042,8 +1021,8 @@ mod ignored_method { impl QueryRoot { fn character(&self) -> Box> { Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -1134,12 +1113,12 @@ mod external_resolver { fn character(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Box::new(Droid { - id: "?????".to_string(), - primary_function: "???".to_string(), + id: "?????".into(), + primary_function: "???".into(), }), }; ch @@ -1178,8 +1157,8 @@ mod external_resolver { let schema = schema(QueryRoot::Droid); let db = Database { droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; @@ -1250,15 +1229,15 @@ mod full_featured { fn character(&self, ctx: &CustomContext) -> Box> { let ch: Box> = match ctx { CustomContext::Human => Box::new(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Box::new(DroidCustomContext { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), CustomContext::Ewok => Box::new(EwokCustomContext { - id: "ewok-1".to_string(), + id: "ewok-1".into(), funny: true, }), }; diff --git a/tests/integration/src/codegen/union_derive.rs b/tests/integration/tests/codegen_union_derive.rs similarity index 90% rename from tests/integration/src/codegen/union_derive.rs rename to tests/integration/tests/codegen_union_derive.rs index be6577e2..034e34d2 100644 --- a/tests/integration/src/codegen/union_derive.rs +++ b/tests/integration/tests/codegen_union_derive.rs @@ -1,12 +1,16 @@ //! Tests for `#[derive(GraphQLUnion)]` macro. +pub mod common; + use std::marker::PhantomData; use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLObject, GraphQLType, GraphQLUnion, RootNode, ScalarValue, + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLObject, + GraphQLUnion, ScalarValue, }; +use self::common::util::{schema, schema_with_scalar}; + #[derive(GraphQLObject)] struct Human { id: String, @@ -53,31 +57,6 @@ struct EwokCustomContext { funny: bool, } -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - mod trivial_enum { use super::*; @@ -97,12 +76,12 @@ mod trivial_enum { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -217,12 +196,12 @@ mod generic_enum { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -317,7 +296,7 @@ mod generic_lifetime_enum { match self { Self::Human => Character::A(LifetimeHuman { id: "human-32" }), Self::Droid => Character::B(GenericDroid { - id: "droid-99".to_string(), + id: "droid-99".into(), _t: PhantomData, }), } @@ -394,8 +373,8 @@ mod description_from_doc_comments { impl QueryRoot { fn character(&self) -> Character { Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -458,8 +437,8 @@ mod explicit_name_and_description { impl QueryRoot { fn character(&self) -> Character { Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -542,12 +521,12 @@ mod explicit_scalar { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -594,7 +573,7 @@ mod explicit_scalar { } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; + use crate::common::MyScalarValue; use super::*; @@ -615,12 +594,12 @@ mod custom_scalar { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -688,12 +667,12 @@ mod explicit_generic_scalar { fn character<__S: ScalarValue>(&self) -> Character<__S> { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -759,12 +738,12 @@ mod bounded_generic_scalar { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } @@ -827,12 +806,12 @@ mod custom_context { fn character(&self, ctx: &CustomContext) -> Character { match ctx { CustomContext::Human => Character::A(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Character::B(DroidCustomContext { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), _ => unimplemented!(), } @@ -896,12 +875,12 @@ mod different_context { fn character(&self, ctx: &CustomContext) -> Character { match ctx { CustomContext::Human => Character::A(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Character::B(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), _ => unimplemented!(), } @@ -966,8 +945,8 @@ mod ignored_enum_variants { impl QueryRoot { fn character(&self) -> Character { Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }) } } @@ -1053,8 +1032,8 @@ mod external_resolver_enum { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B, } @@ -1093,8 +1072,8 @@ mod external_resolver_enum { let schema = schema(QueryRoot::Droid); let db = Database { droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; @@ -1144,12 +1123,12 @@ mod external_resolver_enum_variant { fn character(&self) -> Character { match self { Self::Human => Character::A(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => Character::B(Droid { - id: "?????".to_string(), - primary_function: "???".to_string(), + id: "?????".into(), + primary_function: "???".into(), }), } } @@ -1187,8 +1166,8 @@ mod external_resolver_enum_variant { let schema = schema(QueryRoot::Droid); let db = Database { droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; @@ -1250,15 +1229,15 @@ mod full_featured_enum { fn character(&self, ctx: &CustomContext) -> Character<()> { match ctx { CustomContext::Human => Character::A(HumanCustomContext { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), CustomContext::Droid => Character::B(DroidCustomContext { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), CustomContext::Ewok => Character::C(EwokCustomContext { - id: "ewok-1".to_string(), + id: "ewok-1".into(), funny: true, }), } @@ -1409,7 +1388,7 @@ mod trivial_struct { Self::Human => "human-32", Self::Droid => "droid-99", } - .to_string(), + .into(), } } } @@ -1432,8 +1411,8 @@ mod trivial_struct { let schema = schema(QueryRoot::Human); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), droid: None, }; @@ -1453,8 +1432,8 @@ mod trivial_struct { let db = Database { human: None, droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; @@ -1478,8 +1457,8 @@ mod trivial_struct { let schema = schema(QueryRoot::Human); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), droid: None, }; @@ -1501,8 +1480,8 @@ mod trivial_struct { let schema = schema(QueryRoot::Human); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), droid: None, }; @@ -1524,8 +1503,8 @@ mod trivial_struct { let schema = schema(QueryRoot::Human); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), droid: None, }; @@ -1570,7 +1549,7 @@ mod generic_struct { impl QueryRoot { fn character(&self) -> Character { Character { - id: "human-32".to_string(), + id: "human-32".into(), _s: PhantomData, } } @@ -1590,8 +1569,8 @@ mod generic_struct { let schema = schema(QueryRoot); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), }; @@ -1678,7 +1657,7 @@ mod full_featured_struct { Self::Human => "human-32", Self::Droid => "droid-99", } - .to_string(), + .into(), _s: PhantomData, } } @@ -1702,8 +1681,8 @@ mod full_featured_struct { let schema = schema(QueryRoot::Human); let db = Database { human: Some(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), }), droid: None, }; @@ -1723,8 +1702,8 @@ mod full_featured_struct { let db = Database { human: None, droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), }), }; @@ -1804,12 +1783,12 @@ mod issue_845 { fn character(&self) -> Character { match self { Self::Human => Character::A(Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), + id: "human-32".into(), + home_planet: "earth".into(), })), Self::Droid => Character::B(Arc::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-99".into(), + primary_function: "run".into(), })), } } diff --git a/tests/integration/tests/common/mod.rs b/tests/integration/tests/common/mod.rs new file mode 100644 index 00000000..5dc6a277 --- /dev/null +++ b/tests/integration/tests/common/mod.rs @@ -0,0 +1,147 @@ +use std::fmt; + +use juniper::ScalarValue; +use serde::{de, Deserialize, Deserializer, Serialize}; + +/// Common utilities used across tests. +pub mod util { + use futures::StreamExt as _; + use juniper::{ + graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError, + GraphQLError, GraphQLType, RootNode, ScalarValue, Value, ValuesStream, + }; + + pub fn schema<'q, C, Q>( + query_root: Q, + ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> + where + Q: GraphQLType + 'q, + { + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) + } + + pub fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, + ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> + where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, + { + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) + } + + /// Extracts a single next value from the result returned by + /// [`juniper::resolve_into_stream()`] and transforms it into a regular + /// [`Value`]. + pub async fn extract_next( + input: Result<(Value>, Vec>), GraphQLError>, + ) -> Result<(Value, Vec>), GraphQLError> { + let (stream, errs) = input?; + if !errs.is_empty() { + return Ok((Value::Null, errs)); + } + + if let Value::Object(obj) = stream { + for (name, mut val) in obj { + if let Value::Scalar(ref mut stream) = val { + return match stream.next().await { + Some(Ok(val)) => Ok((graphql_value!({ name: val }), vec![])), + Some(Err(e)) => Ok((Value::Null, vec![e])), + None => Ok((Value::Null, vec![])), + }; + } + } + } + + panic!("Expected to get Value::Object containing a Stream") + } +} + +#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] +#[serde(untagged)] +pub enum MyScalarValue { + #[value(as_float, as_int)] + Int(i32), + Long(i64), + #[value(as_float)] + Float(f64), + #[value(as_str, as_string, into_string)] + String(String), + #[value(as_bool)] + Boolean(bool), +} + +impl<'de> Deserialize<'de> for MyScalarValue { + fn deserialize>(de: D) -> Result { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MyScalarValue; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a valid input value") + } + + fn visit_bool(self, b: bool) -> Result { + Ok(MyScalarValue::Boolean(b)) + } + + fn visit_i32(self, n: i32) -> Result { + Ok(MyScalarValue::Int(n)) + } + + fn visit_i64(self, b: i64) -> Result { + if b <= i64::from(i32::MAX) { + self.visit_i32(b.try_into().unwrap()) + } else { + Ok(MyScalarValue::Long(b)) + } + } + + fn visit_u32(self, n: u32) -> Result { + if n <= i32::MAX as u32 { + self.visit_i32(n.try_into().unwrap()) + } else { + self.visit_u64(n.into()) + } + } + + fn visit_u64(self, n: u64) -> Result { + if n <= i64::MAX as u64 { + self.visit_i64(n.try_into().unwrap()) + } else { + // Browser's `JSON.stringify()` serializes all numbers + // having no fractional part as integers (no decimal point), + // so we must parse large integers as floating point, + // otherwise we would error on transferring large floating + // point numbers. + // TODO: Use `FloatToInt` conversion once stabilized: + // https://github.com/rust-lang/rust/issues/67057 + Ok(MyScalarValue::Float(n as f64)) + } + } + + fn visit_f64(self, f: f64) -> Result { + Ok(MyScalarValue::Float(f)) + } + + fn visit_str(self, s: &str) -> Result { + self.visit_string(s.into()) + } + + fn visit_string(self, s: String) -> Result { + Ok(MyScalarValue::String(s)) + } + } + + de.deserialize_any(Visitor) + } +} diff --git a/tests/integration/src/custom_scalar.rs b/tests/integration/tests/custom_scalar.rs similarity index 50% rename from tests/integration/src/custom_scalar.rs rename to tests/integration/tests/custom_scalar.rs index 7a8c6f94..ac1d31cf 100644 --- a/tests/integration/src/custom_scalar.rs +++ b/tests/integration/tests/custom_scalar.rs @@ -1,95 +1,16 @@ -use std::{convert::TryInto as _, fmt, pin::Pin}; +pub mod common; + +use std::pin::Pin; use futures::{stream, Stream}; use juniper::{ execute, graphql_input_value, graphql_object, graphql_scalar, graphql_subscription, graphql_vars, parser::{ParseError, ScalarToken, Token}, - serde::{de, Deserialize, Deserializer, Serialize}, - EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, ScalarValue, - Value, Variables, + EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, Value, Variables, }; -#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] -#[serde(untagged)] -pub(crate) enum MyScalarValue { - #[value(as_float, as_int)] - Int(i32), - Long(i64), - #[value(as_float)] - Float(f64), - #[value(as_str, as_string, into_string)] - String(String), - #[value(as_bool)] - Boolean(bool), -} - -impl<'de> Deserialize<'de> for MyScalarValue { - fn deserialize>(de: D) -> Result { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MyScalarValue; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("a valid input value") - } - - fn visit_bool(self, b: bool) -> Result { - Ok(MyScalarValue::Boolean(b)) - } - - fn visit_i32(self, n: i32) -> Result { - Ok(MyScalarValue::Int(n)) - } - - fn visit_i64(self, b: i64) -> Result { - if b <= i64::from(i32::MAX) { - self.visit_i32(b.try_into().unwrap()) - } else { - Ok(MyScalarValue::Long(b)) - } - } - - fn visit_u32(self, n: u32) -> Result { - if n <= i32::MAX as u32 { - self.visit_i32(n.try_into().unwrap()) - } else { - self.visit_u64(n.into()) - } - } - - fn visit_u64(self, n: u64) -> Result { - if n <= i64::MAX as u64 { - self.visit_i64(n.try_into().unwrap()) - } else { - // Browser's `JSON.stringify()` serializes all numbers - // having no fractional part as integers (no decimal point), - // so we must parse large integers as floating point, - // otherwise we would error on transferring large floating - // point numbers. - // TODO: Use `FloatToInt` conversion once stabilized: - // https://github.com/rust-lang/rust/issues/67057 - Ok(MyScalarValue::Float(n as f64)) - } - } - - fn visit_f64(self, f: f64) -> Result { - Ok(MyScalarValue::Float(f)) - } - - fn visit_str(self, s: &str) -> Result { - self.visit_string(s.into()) - } - - fn visit_string(self, s: String) -> Result { - Ok(MyScalarValue::String(s)) - } - } - - de.deserialize_any(Visitor) - } -} +use self::common::MyScalarValue; #[graphql_scalar(with = long, scalar = MyScalarValue)] type Long = i64; @@ -104,16 +25,16 @@ mod long { pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value::() .copied() - .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) + .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {v}")) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::Int(v) = value { v.parse() - .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) + .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: i64| s.into()) } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) + Err(ParseError::unexpected_token(Token::Scalar(value))) } } } @@ -153,7 +74,7 @@ where assert_eq!(errs, []); - println!("Result: {:?}", result); + println!("Result: {result:?}"); let obj = result.as_object_value().expect("Result is not an object"); diff --git a/tests/integration/tests/cve_2022_31173.rs b/tests/integration/tests/cve_2022_31173.rs new file mode 100644 index 00000000..332306a3 --- /dev/null +++ b/tests/integration/tests/cve_2022_31173.rs @@ -0,0 +1,56 @@ +//! Checks that long looping chain of fragments doesn't cause a stack overflow. +//! +//! ```graphql +//! # Fragment loop example +//! query { +//! ...a +//! } +//! +//! fragment a on Query { +//! ...b +//! } +//! +//! fragment b on Query { +//! ...a +//! } +//! ``` + +use std::iter; + +use itertools::Itertools as _; +use juniper::{graphql_object, graphql_vars, EmptyMutation, EmptySubscription}; + +struct Query; + +#[graphql_object] +impl Query { + fn dummy() -> bool { + false + } +} + +type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; + +#[tokio::test] +async fn test() { + const PERM: &str = "abcefghijk"; + const CIRCLE_SIZE: usize = 7500; + + let query = iter::once(format!("query {{ ...{PERM} }} ")) + .chain( + PERM.chars() + .permutations(PERM.len()) + .map(|vec| vec.into_iter().collect::()) + .take(CIRCLE_SIZE) + .collect::>() + .into_iter() + .circular_tuple_windows::<(_, _)>() + .map(|(cur, next)| format!("fragment {cur} on Query {{ ...{next} }} ")), + ) + .collect::(); + + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + let _ = juniper::execute(&query, None, &schema, &graphql_vars! {}, &()) + .await + .unwrap_err(); +} diff --git a/tests/integration/src/explicit_null.rs b/tests/integration/tests/explicit_null.rs similarity index 100% rename from tests/integration/src/explicit_null.rs rename to tests/integration/tests/explicit_null.rs diff --git a/tests/integration/tests/infallible_as_field_error.rs b/tests/integration/tests/infallible_as_field_error.rs new file mode 100644 index 00000000..43293264 --- /dev/null +++ b/tests/integration/tests/infallible_as_field_error.rs @@ -0,0 +1,12 @@ +use std::convert::Infallible; + +use juniper::graphql_object; + +struct Query; + +#[graphql_object] +impl Query { + fn ping() -> Result { + Ok(false) + } +} diff --git a/tests/integration/src/inside_macro.rs b/tests/integration/tests/inside_macro.rs similarity index 100% rename from tests/integration/src/inside_macro.rs rename to tests/integration/tests/inside_macro.rs diff --git a/tests/integration/src/issue_371.rs b/tests/integration/tests/issue_371.rs similarity index 100% rename from tests/integration/src/issue_371.rs rename to tests/integration/tests/issue_371.rs diff --git a/tests/integration/src/issue_372.rs b/tests/integration/tests/issue_372.rs similarity index 100% rename from tests/integration/src/issue_372.rs rename to tests/integration/tests/issue_372.rs diff --git a/tests/integration/src/issue_398.rs b/tests/integration/tests/issue_398.rs similarity index 100% rename from tests/integration/src/issue_398.rs rename to tests/integration/tests/issue_398.rs diff --git a/tests/integration/src/issue_407.rs b/tests/integration/tests/issue_407.rs similarity index 93% rename from tests/integration/src/issue_407.rs rename to tests/integration/tests/issue_407.rs index 245398c3..a8a4ef13 100644 --- a/tests/integration/src/issue_407.rs +++ b/tests/integration/tests/issue_407.rs @@ -31,12 +31,12 @@ struct Droid { impl Query { fn characters() -> Vec { let human = Human { - id: "1".to_string(), - name: "Han Solo".to_string(), + id: "1".into(), + name: "Han Solo".into(), }; let droid = Droid { - id: "2".to_string(), - serial_number: "234532545235".to_string(), + id: "2".into(), + serial_number: "234532545235".into(), }; vec![Into::into(human), Into::into(droid)] } diff --git a/tests/integration/src/issue_500.rs b/tests/integration/tests/issue_500.rs similarity index 100% rename from tests/integration/src/issue_500.rs rename to tests/integration/tests/issue_500.rs diff --git a/tests/integration/src/issue_798.rs b/tests/integration/tests/issue_798.rs similarity index 95% rename from tests/integration/src/issue_798.rs rename to tests/integration/tests/issue_798.rs index fed322fc..40252406 100644 --- a/tests/integration/src/issue_798.rs +++ b/tests/integration/tests/issue_798.rs @@ -42,12 +42,12 @@ impl Query { fn field(&self) -> FieldResult { match self { Self::Human => FieldResult::Human(Human { - id: "human-32".to_owned(), - home_planet: "earth".to_owned(), + id: "human-32".into(), + home_planet: "earth".into(), }), Self::Droid => FieldResult::Droid(Droid { - id: "droid-99".to_owned(), - primary_function: "run".to_owned(), + id: "droid-99".into(), + primary_function: "run".into(), }), } } diff --git a/tests/integration/src/issue_914.rs b/tests/integration/tests/issue_914.rs similarity index 100% rename from tests/integration/src/issue_914.rs rename to tests/integration/tests/issue_914.rs diff --git a/tests/integration/src/issue_922.rs b/tests/integration/tests/issue_922.rs similarity index 96% rename from tests/integration/src/issue_922.rs rename to tests/integration/tests/issue_922.rs index fd28b45f..1a89a68e 100644 --- a/tests/integration/src/issue_922.rs +++ b/tests/integration/tests/issue_922.rs @@ -14,11 +14,11 @@ impl Query { vec![ Into::into(Human { id: 0, - name: "human-32".to_owned(), + name: "human-32".into(), }), Into::into(Droid { id: 1, - name: "R2-D2".to_owned(), + name: "R2-D2".into(), }), ] } diff --git a/tests/integration/src/issue_925.rs b/tests/integration/tests/issue_925.rs similarity index 100% rename from tests/integration/src/issue_925.rs rename to tests/integration/tests/issue_925.rs diff --git a/tests/integration/src/issue_945.rs b/tests/integration/tests/issue_945.rs similarity index 95% rename from tests/integration/src/issue_945.rs rename to tests/integration/tests/issue_945.rs index 369e660b..42458313 100644 --- a/tests/integration/src/issue_945.rs +++ b/tests/integration/tests/issue_945.rs @@ -13,8 +13,8 @@ impl Query { fn artoo() -> Character { Character::Droid(Droid { id: 1, - name: "R2-D2".to_owned(), - sensor_color: "red".to_owned(), + name: "R2-D2".into(), + sensor_color: "red".into(), }) } } diff --git a/tests/integration/src/pre_parse.rs b/tests/integration/tests/pre_parse.rs similarity index 92% rename from tests/integration/src/pre_parse.rs rename to tests/integration/tests/pre_parse.rs index ec987eca..df2f97e3 100644 --- a/tests/integration/src/pre_parse.rs +++ b/tests/integration/tests/pre_parse.rs @@ -1,3 +1,5 @@ +use std::pin::Pin; + use futures::{Stream, StreamExt, TryFutureExt}; use juniper::{ executor::{execute_validated_query_async, get_operation, resolve_validated_subscription}, @@ -6,7 +8,6 @@ use juniper::{ validation::{validate_input_values, visit_all_rules, ValidatorContext}, EmptyMutation, FieldError, OperationType, RootNode, }; -use std::pin::Pin; pub struct Context; @@ -64,15 +65,10 @@ async fn query_document_can_be_pre_parsed() { let errors = validate_input_values(&graphql_vars! {}, operation, &root_node.schema); assert!(errors.is_empty()); - let (_, errors) = execute_validated_query_async( - &document, - operation, - root_node, - &graphql_vars! {}, - &Context {}, - ) - .await - .unwrap(); + let (_, errors) = + execute_validated_query_async(&document, operation, root_node, &graphql_vars! {}, &Context) + .await + .unwrap(); assert!(errors.len() == 0); } @@ -92,7 +88,7 @@ async fn subscription_document_can_be_pre_parsed() { &operation, &root_node, &graphql_vars! {}, - &Context {}, + &Context, ) .map_ok(|(stream, errors)| juniper_subscriptions::Connection::from_stream(stream, errors)) .await