Repository: davidpdrsn/juniper-from-schema Branch: master Commit: 90f1e3317370 Files: 141 Total size: 380.7 KB Directory structure: gitextract_45z7vqcr/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── bin/ │ ├── release │ └── run_all_examples ├── generate-subscription-tests/ │ ├── Cargo.toml │ ├── generated_files/ │ │ └── .gitkeep │ └── src/ │ └── main.rs ├── juniper-from-schema/ │ ├── Cargo.toml │ ├── README.md │ ├── examples/ │ │ ├── async.rs │ │ ├── default_argument_values.rs │ │ ├── enumeration_types.rs │ │ ├── hello_world.rs │ │ ├── input_types.rs │ │ ├── interface.rs │ │ ├── query_trails.rs │ │ ├── subscription.rs │ │ └── union_types.rs │ ├── src/ │ │ └── lib.rs │ └── tests/ │ ├── compile_fail/ │ │ ├── docs_on_special_case_scalars.rs │ │ ├── docs_on_special_case_scalars.stderr │ │ ├── invalid_as_ref_type.rs │ │ ├── invalid_as_ref_type.stderr │ │ ├── invalid_date_time_scalar_directive.rs │ │ ├── invalid_date_time_scalar_directive.stderr │ │ ├── invalid_juniper_directive_definition.rs │ │ ├── invalid_juniper_directive_definition.stderr │ │ ├── invalid_stream_return_type.rs │ │ ├── invalid_stream_return_type.stderr │ │ ├── scalar_with_built_in_name.rs │ │ ├── scalar_with_built_in_name.stderr │ │ ├── snake_cased_fields_on_input_object_types.rs │ │ ├── snake_cased_fields_on_input_object_types.stderr │ │ ├── snake_cased_fields_on_interfaces.rs │ │ ├── snake_cased_fields_on_interfaces.stderr │ │ ├── snake_cased_fields_on_types.rs │ │ ├── snake_cased_fields_on_types.stderr │ │ ├── unknown_directive.rs │ │ ├── unknown_directive.stderr │ │ ├── unsupported_config.rs │ │ ├── unsupported_config.stderr │ │ ├── uppercase_uuid.rs │ │ └── uppercase_uuid.stderr │ ├── compile_pass/ │ │ ├── async.rs │ │ ├── async_as_ref.rs │ │ ├── async_field_returning_type.rs │ │ ├── async_returning_reference.rs │ │ ├── correct_executor_signature.rs │ │ ├── custom_scalar.rs │ │ ├── customizing_context_name.rs │ │ ├── customizing_the_error_type.rs │ │ ├── dates_and_times.rs │ │ ├── directive_definitions_are_allowed.rs │ │ ├── empty_mutations.rs │ │ ├── enums.rs │ │ ├── field_args.rs │ │ ├── infallible_directive.rs │ │ ├── input_object.rs │ │ ├── input_object_clone.rs │ │ ├── input_objects_have_public_fields.rs │ │ ├── naive_date_time.rs │ │ ├── non_null_list_non_null_items.rs │ │ ├── non_null_list_nullable_items.rs │ │ ├── nullable_list_non_null_items.rs │ │ ├── nullable_list_nullable_items.rs │ │ ├── ownership_attributes.rs │ │ ├── query_trail.rs │ │ ├── query_trail_methods_for_interfaces.rs │ │ ├── query_trail_methods_for_union_types.rs │ │ ├── returning_references.rs │ │ ├── setup.rs │ │ ├── simple_non_null_scalars.rs │ │ ├── simple_nullable_scalars.rs │ │ ├── url.rs │ │ ├── uuid.rs │ │ └── valid_juniper_directive_definition.rs │ ├── converting_query_trails_test.rs │ ├── default_argument_values_test.rs │ ├── doc_test.rs │ ├── end_to_end_test.rs │ ├── launchpad.rs │ ├── query_trail_arguments.rs │ ├── schemas/ │ │ ├── complex_schema.graphql │ │ ├── customizing_context_name.graphql │ │ ├── doc_schema.graphql │ │ ├── doc_test.graphql │ │ ├── original_complex.graphql │ │ ├── returning_references.graphql │ │ └── very_simple_schema.graphql │ ├── subscriptions/ │ │ ├── fail/ │ │ │ ├── stream_item_type_not_in_subscription_field.rs │ │ │ ├── stream_item_type_not_in_subscription_field.stderr │ │ │ ├── stream_type_not_in_subscription_field.rs │ │ │ ├── stream_type_not_in_subscription_field.stderr │ │ │ ├── subscriptions_cannot_implement_interfaces.rs │ │ │ └── subscriptions_cannot_implement_interfaces.stderr │ │ ├── pass/ │ │ │ ├── ownership_owned_infallible_false_async_false_stream_type_UserStream_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_false_async_false_stream_type_UserStream_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_false_async_false_stream_type_default_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_false_async_false_stream_type_default_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_false_async_true_stream_type_UserStream_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_false_async_true_stream_type_UserStream_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_false_async_true_stream_type_default_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_false_async_true_stream_type_default_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_true_async_false_stream_type_UserStream_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_true_async_false_stream_type_UserStream_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_true_async_false_stream_type_default_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_true_async_false_stream_type_default_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_true_async_true_stream_type_UserStream_stream_item_infallible_false.rs │ │ │ ├── ownership_owned_infallible_true_async_true_stream_type_UserStream_stream_item_infallible_true.rs │ │ │ ├── ownership_owned_infallible_true_async_true_stream_type_default_stream_item_infallible_false.rs │ │ │ └── ownership_owned_infallible_true_async_true_stream_type_default_stream_item_infallible_true.rs │ │ └── subscription_setup.rs │ └── version-numbers.rs ├── juniper-from-schema-build/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── juniper-from-schema-build-tests/ │ ├── basic/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── lib.rs │ └── file/ │ ├── Cargo.toml │ ├── build.rs │ ├── schema.graphql │ └── src/ │ └── lib.rs ├── juniper-from-schema-code-gen/ │ ├── Cargo.toml │ └── src/ │ ├── ast_pass/ │ │ ├── code_gen_pass/ │ │ │ ├── gen_query_trails.rs │ │ │ └── mod.rs │ │ ├── directive_parsing.rs │ │ ├── error.rs │ │ ├── mod.rs │ │ ├── schema_visitor.rs │ │ └── validations.rs │ └── lib.rs ├── juniper-from-schema-proc-macro/ │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── lib.rs │ │ └── parse_input.rs │ └── tests/ │ └── version-numbers.rs └── rustfmt.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push] jobs: check-formatting: name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Check formatting run: cargo +stable fmt -- --check test-on-nightly: name: Test on nightly runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # caching build artifacts - name: Cache cargo registry uses: actions/cache@v1 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v1 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v1 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Use nightly run: rustup override set nightly && rustup show - name: Run tests run: cargo test --all - name: Run examples run: bin/run_all_examples test-on-stable: name: Test on stable runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # caching build artifacts - name: Cache cargo registry uses: actions/cache@v1 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v1 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v1 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Use stable run: rustup override set stable && rustup show - name: Run tests run: cargo test --all - name: Run examples run: bin/run_all_examples ================================================ FILE: .gitignore ================================================ /target **/*.rs.bk Cargo.lock /tmp generate-subscription-tests/generated_files/*.rs ================================================ FILE: CHANGELOG.md ================================================ # Change Log All user visible changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/), as described for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md) ## Unreleased - Async resolvers are now supported (requires juniper 0.15). - Subscriptions are now supported (requires juniper 0.15). - Give nice error if you declare custom scalar with same name as built-in. - Re-export juniper from juniper-from-schema to make sure we're always using the same version. - Support generating code from `build.rs` instead of a procedural macro. See the docs for more details. #### Breaking changes The `executor` argument in `field_*` methods now requires two lifetime arguments: ```rust fn field_hello_world<'r, 'a>( &self, executor: &Executor<'r, 'a, Context> ) -> FieldResult { todo!() } ``` This is due to breaking change in juniper. However with a recent version of Rust you can actually skip declaring the lifetimes at all: ```rust fn field_hello_world( &self, executor: &Executor ) -> FieldResult { todo!() } ``` ## [0.5.2] - 2020-02-19 - Remove `MakeQueryTrail` trait. This is not a breaking change since user's shouldn't be using it. - Require the UUID scalar type to be named `Uuid`. This is to remain consistent with the uuid crate. This is not considered a breaking change since it fixes a bug in code generation with inconsistent case. See [#104](https://github.com/davidpdrsn/juniper-from-schema/issues/104). - Add derive `Clone` to input types. See [#110](https://github.com/davidpdrsn/juniper-from-schema/issues/110) - Support all directive definitions in schema but require specific definition for `@juniper`. See the docs for valid definiton of `@juniper`. juniper-from-schema doesn't require you to define `@juniper` in your schema, but some other external tools might. ## [0.5.1] - 2019-11-14 - Support making fields infallible with `@juniper(infallible: true)`. - Make sure fields on input object structs are in fact public. ## [0.5.0] - 2019-10-17 #### Breaking changes - Remove the second lifetime from generated structs for field arguments. Turns out `'a` from `QueryTrail<'a, _, _>` is all we need. - The `DateTime` scalar has been renamed to `DateTimeUtc` to clearly communicate the name Juniper gives it. ## [0.4.2] - 2019-10-16 - Add support for inspecting arguments to `QueryTrail`. See docs for more info. [#83](https://github.com/davidpdrsn/juniper-from-schema/pull/83). ## [0.4.1] - 2019-10-16 ### Fixed - Correctly trigger rebuild of Rust schema if only GraphQL schema changed when using `graphql_schema_from_file!`. ## [0.4.0] - 2019-10-05 ### Added - Support converting `QueryTrail`s for interfaces or unions into `QueryTrail`s for implementors of those interfaces or unions. This makes it possible to use [juniper-eager-loading](https://crates.io/crates/juniper-eager-loading) with interface or union types. [#63](https://github.com/davidpdrsn/juniper-from-schema/pull/63) ### Changed - The `QueryTrail` type is now part of this library rather than being emitted as part of the generated code. This was done so other libraries could make sure of the type. If you're getting errors about missing methods adding `use crate::graphql_schema::query_trails::*` to you module should fix it. [#82](https://github.com/davidpdrsn/juniper-from-schema/pull/82) ## [0.3.2] - 2019-09-30 ### Added - Juniper 0.14 support. ## [0.3.1] - 2019-09-24 ### Added - Support customizing the name of the context type. [#66](https://github.com/davidpdrsn/juniper-from-schema/pull/66) - Improve documentation of special case scalar types. - Implement common traits for generated scalar types. - Support converting `DateTime` into `chrono::NaiveDateTime` for those who don't care about time zones. The behavior can be customized with the `@juniper(with_time_zone: true|false)` directive on the scalar definition. ### Changed - Special case scalars `Date`, `DateTime`, `Uuid`, and `Url` no longer support descriptions in the GraphQL schema. See [#69](https://github.com/davidpdrsn/juniper-from-schema/pull/69) for more details. ### Fixed - Fix support for special case [`Uuid`](https://crates.io/crates/uuid) and [`Url`](https://crates.io/crates/url) scalars. [#69](https://github.com/davidpdrsn/juniper-from-schema/pull/69) - Removed some deprecation warnings related to scalar types. [#78](https://github.com/davidpdrsn/juniper-from-schema/pull/78) - Don't deprecate enum variants in Rust code when marked as deprecated in the schema. ## [0.3.0] - 2019-06-18 - Will now fail to compile if you schema contains field names in snake_case. [#47](https://github.com/davidpdrsn/juniper-from-schema/issues/47) ## [0.2.3] - 2019-06-15 ### Fixed - Fix `QueryTrail` walk methods always returning `false` for fields defined using snake_case in the schema. [#46](https://github.com/davidpdrsn/juniper-from-schema/pull/46) ## [0.2.2] - 2019-06-10 ### Added - Add support for "as_ref" ownership types such as `FieldResult>`. ## [0.2.1] - 2019-05-24 ### Changed - Break crate into two: One that exposes shared types, one that does the code generation. Should not be a breaking change. ## [0.2.0] - 2019-05-08 ### Changed - Replace the magic `#[ownership(owned)]` comment with a schema directive. ## [0.1.7] - 2019-05-07 ### Added - Support default values for input objects. - Support `deprecated` directives on fields and enum values. ### Fixed - Ensure enum variants used for default values exist. This would previously be a run time error. It is now a compile time error. ## [0.1.6] - 2019-05-04 ### Added - Much better error messages. Basically a rip-off of Rust's compiler errors. ### Changed - The `ID` GraphQL type now gets generated into `juniper::ID` instead of a custom newtype wrapper. This is a breaking change but should be straight forward to fix. ### Fixed - Correctly panic if schema contains unsupported features such as directives or a subscription type. - Correctly generate docs for all types. - Fix compile error when using custom scalar. ## [0.1.5] - 2019-04-26 ### Added - Make default argument values work for floats, integers, strings, booleans, enumerations, and lists (of supported types). Objects, variables, and nulls are not supported. ### Fixed - Field methods that return enumerations no longer get `QueryTrails`. You couldn't really do anything with them since enumerations cannot contain data. - Schemes that don't contain a root mutation type now doesn't fail to compile. It would use `()` for the context, when it should have used `Context`. ## [0.1.4] - 2019-02-16 ### Fixed - Make `graphql_schema_from_file!` look from same folder as your `Cargo.toml`. This fixes issues with finding the schema within a [workspace](https://doc.rust-lang.org/book/second-edition/ch14-03-cargo-workspaces.html) project. Should not be a breaking change. - Added missing `juniper::` qualifications for generated code that referenced `Executor`. - Many documentation improvements, including: - Table of contents - Description of how to customize the error type - Clearer example code by removing `use juniper::*` - Description of how `QueryTrail` works for `Vec` and `Option` - Describe how to view the generated code - Several typo fixes ## [0.1.3] - 2019-02-01 ### Fixed - `QueryTrail` methods now generated for union types. ## [0.1.2] - 2019-01-14 ### Fixed - `QueryTrail` methods now generated for interface types. ## [0.1.1] - 2018-12-21 Just fixed broken homepage link on crates.io ## 0.1.0 - 2018-12-21 Initial release. [0.5.2]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.5.1...0.5.2 [0.5.1]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.4.2...0.5.0 [0.4.2]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.4.1...0.4.2 [0.4.1]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.3.2...0.4.0 [0.3.2]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.3.1...0.3.2 [0.3.1]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.3.0...0.3.1 [0.3.0]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.2.3...0.3.0 [0.2.3]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.7...0.2.0 [0.1.7]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.6...0.1.7 [0.1.6]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.5...0.1.6 [0.1.5]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.4...0.1.5 [0.1.4]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.3...0.1.4 [0.1.3]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.2...0.1.3 [0.1.2]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.1...0.1.2 [0.1.1]: https://github.com/davidpdrsn/juniper-from-schema/compare/0.1.0...0.1.1 ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "juniper-from-schema", "juniper-from-schema-proc-macro", "juniper-from-schema-code-gen", "juniper-from-schema-build", # Tests "juniper-from-schema-build-tests/basic", "juniper-from-schema-build-tests/file", ] exclude = ["generate-subscription-tests"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 David Grynnerup Pedersen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # [juniper-from-schema](https://crates.io/crates/juniper-from-schema) ![Build](https://github.com/davidpdrsn/juniper-from-schema/workflows/Build/badge.svg) [![crates.io](https://meritbadge.herokuapp.com/juniper-from-schema)](https://crates.io/crates/juniper-from-schema) [![Documentation](https://docs.rs/juniper-from-schema/badge.svg)](https://docs.rs/juniper-from-schema) This library contains a procedural macro that reads a GraphQL schema file, and generates the corresponding [Juniper](https://crates.io/crates/juniper) [macro calls]. This means you can have a real schema file and be guaranteed that it matches your Rust implementation. It also removes most of the boilerplate involved in using Juniper. [macro calls]: https://graphql-rust.github.io/types/objects/complex_fields.html # Looking for juniper 0.15 support? The version of juniper-from-schema that is released on crates.io (0.5.2) doesn't support juniper 0.15. However the master branch does! So you will have to use a git dependency for now. We plan to do an official release soon. Follow [this](https://github.com/davidpdrsn/juniper-from-schema/milestone/1) milestone to see whats left. # Example Imagine you have a GraphQL schema like this: ```graphql schema { query: Query } type Query { helloWorld(name: String!): String! @juniper(ownership: "owned") } ``` That can be implemented like so: ```rust use juniper_from_schema::graphql_schema_from_file; // This is the important line graphql_schema_from_file!("readme_schema.graphql"); pub struct Context; impl juniper::Context for Context {} pub struct Query; // This trait is generated by `graphql_schema_from_file!` based on the schema impl QueryFields for Query { fn field_hello_world( &self, _executor: &juniper::Executor, name: String, ) -> juniper::FieldResult { Ok(format!("Hello, {}!", name)) } } fn main() { let ctx = Context; let query = "query { helloWorld(name: \"Ferris\") }"; let (result, errors) = juniper::execute_sync( query, None, &Schema::new(Query, juniper::EmptyMutation::new()), &juniper::Variables::new(), &ctx, ) .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( result .as_object_value() .unwrap() .get_field_value("helloWorld") .unwrap() .as_scalar_value::() .unwrap(), "Hello, Ferris!", ); } ``` See the [crate documentation](https://docs.rs/juniper-from-schema/) for a usage examples and more info. # N+1s If you're having issues with N+1 query bugs consider using [juniper-eager-loading](https://crates.io/crates/juniper-eager-loading). It was built to integrate seamlessly with juniper-from-schema. # Development ## If you're seeing `No such file or directory (os error 2)` when running the tests This might be caused by setting `CARGO_TARGET_DIR`. Setting that env var changes the directory the [trybuild](https://crates.io/crates/trybuild) tests are run from which means all the paths to the test schemas no longer match. The only workaround is to unset `CARGO_TARGET_DIR` when working on juniper-from-schema. I recommend [direnv](https://github.com/direnv/direnv) to unset the env var only this directory and not globally. ================================================ FILE: bin/release ================================================ #!/bin/bash set -e cd ./juniper-from-schema-code-gen cargo release --no-dev-version cd ./juniper-from-schema-build cargo release --no-dev-version cd ./juniper-from-schema-proc-macro cargo release --no-dev-version cd ../juniper-from-schema cargo release --no-dev-version ================================================ FILE: bin/run_all_examples ================================================ #!/bin/bash set -e main() { cd juniper-from-schema find examples -name "*.rs" | while read f; do cargo run --example "`echo $f | sed "s/examples\///g" | sed "s/\.rs//g"`"; done } main ================================================ FILE: generate-subscription-tests/Cargo.toml ================================================ [package] name = "all-subscription-options" version = "0.1.0" authors = ["David Pedersen "] edition = "2018" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: generate-subscription-tests/generated_files/.gitkeep ================================================ ================================================ FILE: generate-subscription-tests/src/main.rs ================================================ // This crate is used to generate the tests you'll find in "juniper-from-schema/tests/subscriptions." // // There are quite a few ways in which directives interact so we generate the tests to make sure we // cover all the cases. // // The generated files will be placed in // "juniper-from-schema/generate-subscription-tests/generated_files" so currently you manually have // to move them into "juniper-from-schema/tests/subscriptions" fn main() { let combinations = Ownership::all() .flat_map(|ownership| { Infallible::all().flat_map(move |infallible| { Async::all().flat_map(move |async_| { StreamType::all().flat_map(move |stream_type| { StreamItemInfallible::all().map(move |stream_item_infallible| { ( ownership, infallible, async_, stream_type, stream_item_infallible, ) }) }) }) }) }) .collect::>(); for (ownership, infallible, async_, stream_type, stream_item_infallible) in combinations { let args = vec![ ownership.to_string(), infallible.to_string(), async_.to_string(), stream_type.to_string(), stream_item_infallible.to_string(), ] .into_iter() .filter_map(|item| item) .collect::>() .join(", "); let return_type = match (stream_type, infallible, stream_item_infallible, ownership) { (_, _, _, Ownership::AsRef) => continue, (_, _, _, Ownership::Borrowed) => continue, (StreamType::Default, Infallible::True, StreamItemInfallible::True, Ownership::Owned) => { "BoxStream" } (StreamType::Default, Infallible::True, StreamItemInfallible::False, Ownership::Owned) => { "BoxStream>" } (StreamType::Default, Infallible::False, StreamItemInfallible::True, Ownership::Owned) => { "FieldResult>" } (StreamType::Default, Infallible::False, StreamItemInfallible::False, Ownership::Owned) => { "FieldResult>>" } (StreamType::Custom, Infallible::True, _, _) => "UserStream", (StreamType::Custom, Infallible::False, _, _) => "FieldResult", }; let async_trait = match async_ { Async::True => "#[async_trait]\n", Async::False => "", }; let async_str = match async_ { Async::True => "async ", Async::False => "", }; let stream_item_ty = match stream_item_infallible { StreamItemInfallible::True => "User", StreamItemInfallible::False => "FieldResult", }; let user_stream = if return_type.contains("UserStream") { format!(r#"pub struct UserStream; impl Stream for UserStream {{ type Item = {}; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> {{ todo!() }} }}"#, stream_item_ty) } else { String::new() }; let body = "todo!()"; let code = format!( r##"#![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! {{ type Query {{ ping: Boolean! }} type Subscription {{ users: User! @juniper({args}) }} type User {{ id: ID! name: String! }} schema {{ query: Query subscription: Subscription }} }} {async_trait}impl SubscriptionFields for Subscription {{ {async}fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> {type} {{ {body} }} }} {user_stream}"##, args = args, type = return_type, body = body, async = async_str, async_trait = async_trait, user_stream = user_stream ); let file_name = format!( "generated_files/{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.rs", ownership.name(), ownership.variant().unwrap_or("default").replace('"', ""), infallible.name(), infallible.variant().unwrap_or("default"), async_.name(), async_.variant().unwrap_or("default"), stream_type.name(), stream_type.variant().unwrap_or("default").replace('"', ""), stream_item_infallible.name(), stream_item_infallible.variant().unwrap_or("default"), ); std::fs::write(file_name, code.as_bytes()).unwrap(); } } macro_rules! def_enum { ( ($name:ident, $name_str:expr), [$( ($variant:ident, $variant_str:expr) ),* $(,)?] ) => { #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum $name { $($variant),* } impl $name { fn all() -> impl Iterator { vec![ $($name::$variant),* ].into_iter() } fn name(&self) -> &'static str { match self { $( $name::$variant => $name_str, )* } } fn variant(&self) -> Option<&'static str> { match self { $( $name::$variant => $variant_str, )* } } fn to_string(&self) -> Option { if let Some(s) = self.variant() { Some(format!("{}: {}", $name_str, s)) } else { None } } } }; } def_enum!( (Ownership, "ownership"), [ (Borrowed, Some("\"borrowed\"")), (AsRef, Some("\"as_ref\"")), (Owned, Some("\"owned\"")) ] ); def_enum!( (Infallible, "infallible"), [(True, Some("true")), (False, Some("false"))] ); def_enum!( (Async, "async"), [(True, Some("true")), (False, Some("false"))] ); def_enum!( (StreamType, "stream_type"), [(Default, None), (Custom, Some("\"UserStream\""))] ); def_enum!( (StreamItemInfallible, "stream_item_infallible"), [(True, Some("true")), (False, Some("false"))] ); ================================================ FILE: juniper-from-schema/Cargo.toml ================================================ [package] version = "0.5.2" authors = ["David Pedersen "] categories = ["web-programming"] description = "Generate Juniper code from you GraphQL schema" documentation = "https://docs.rs/juniper-from-schema" edition = "2018" homepage = "https://github.com/davidpdrsn/juniper-from-schema" keywords = ["web", "graphql", "juniper"] license = "MIT" name = "juniper-from-schema" readme = "README.md" repository = "https://github.com/davidpdrsn/juniper-from-schema.git" [dependencies] juniper-from-schema-proc-macro = { version = "0.5.2", path = "../juniper-from-schema-proc-macro" } juniper = "0.15" futures = "0.3" [dev_dependencies] serde_json = "1" assert-json-diff = "0.2" maplit = "1" version-sync = "0.8" trybuild = "1" rustversion = "0.1" uuid = { version = "0.8", features = ["v4"] } url = "2" chrono = "0.4" async-trait = "0.1" tokio = { version = "0.2", features = ["sync", "stream"] } ================================================ FILE: juniper-from-schema/README.md ================================================ # [juniper-from-schema](https://crates.io/crates/juniper-from-schema) [![crates.io](https://meritbadge.herokuapp.com/juniper-from-schema)](https://crates.io/crates/juniper-from-schema) [![Documentation](https://docs.rs/juniper-from-schema/badge.svg)](https://docs.rs/juniper-from-schema) This library contains a procedural macro that reads a GraphQL schema file, and generates the corresponding [Juniper](https://crates.io/crates/juniper) [macro calls]. This means you can have a real schema file and be guaranteed that it matches your Rust implementation. It also removes most of the boilerplate involved in using Juniper. [macro calls]: https://graphql-rust.github.io/types/objects/complex_fields.html See the [crate documentation](https://docs.rs/juniper-from-schema/) for a usage examples and more info. ================================================ FILE: juniper-from-schema/examples/async.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use async_trait::async_trait; use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query } type Query { findTweet(id: ID!): [Tweet!]! @juniper(ownership: "owned", async: true) } type Tweet { id: ID! text: String! } } pub struct Context; impl juniper::Context for Context {} pub struct Tweet { id: ID, text: String, } impl TweetFields for Tweet { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_text(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Query; #[async_trait] impl QueryFields for Query { async fn field_find_tweet<'s, 'r, 'a>( &'s self, executor: &Executor<'r, 'a, Context>, trail: &QueryTrail<'r, Tweet, Walked>, id: ID, ) -> FieldResult> { let tweets = vec![Tweet { id, text: String::from("Hello, World!"), }]; Ok(tweets) } } fn main() {} ================================================ FILE: juniper-from-schema/examples/default_argument_values.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query } enum Status { PUBLISHED UNPUBLISHED } type Query { allPosts(status: Status = PUBLISHED): [Post!]! @juniper(ownership: "owned") } type Post { id: ID! } } pub struct Context; impl juniper::Context for Context {} pub struct Post { id: ID, } impl PostFields for Post { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { Ok(&self.id) } } pub struct Query; impl QueryFields for Query { fn field_all_posts( &self, executor: &Executor, trail: &QueryTrail, status: Status, ) -> FieldResult> { // `status` will be `Status::Published` if not given in the query match status { Status::Published => unimplemented!("find published posts"), Status::Unpublished => unimplemented!("find unpublished posts"), } } } fn main() {} ================================================ FILE: juniper-from-schema/examples/enumeration_types.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query } enum Status { PUBLISHED UNPUBLISHED } type Query { allPosts(status: Status!): [Post!]! @juniper(ownership: "owned") } type Post { id: ID! } } fn main() {} pub struct Context; impl juniper::Context for Context {} pub struct Post { id: ID, } impl PostFields for Post { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { Ok(&self.id) } } pub struct Query; impl QueryFields for Query { fn field_all_posts( &self, executor: &Executor, trail: &QueryTrail, status: Status, ) -> FieldResult> { match status { Status::Published => unimplemented!("find published posts"), Status::Unpublished => unimplemented!("find unpublished posts"), } } } ================================================ FILE: juniper-from-schema/examples/hello_world.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; // This is the important line graphql_schema! { schema { query: Query mutation: Mutation } type Query { // The directive makes the return value `FieldResult` // rather than the default `FieldResult<&String>` helloWorld(name: String!): String! @juniper(ownership: "owned") } type Mutation { noop: Boolean! } } pub struct Context; impl juniper::Context for Context {} pub struct Query; impl QueryFields for Query { fn field_hello_world(&self, executor: &Executor, name: String) -> FieldResult { Ok(format!("Hello, {}!", name)) } } pub struct Mutation; impl MutationFields for Mutation { fn field_noop(&self, executor: &Executor) -> FieldResult<&bool> { Ok(&true) } } fn main() { let ctx = Context; let query = "query { helloWorld(name: \"Ferris\") }"; let (result, errors) = juniper::execute_sync( query, None, &Schema::new(Query, Mutation, juniper::EmptySubscription::new()), &Variables::new(), &ctx, ) .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( result .as_object_value() .unwrap() .get_field_value("helloWorld") .unwrap() .as_scalar_value::() .unwrap(), "Hello, Ferris!", ); } ================================================ FILE: juniper-from-schema/examples/input_types.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query mutation: Mutation } type Mutation { createPost(input: CreatePost!): Post @juniper(ownership: "owned") } input CreatePost { title: String! } type Post { id: ID! title: String! } type Query { noop: Boolean! } } fn main() {} pub struct Context; impl juniper::Context for Context {} pub struct Post { id: ID, } impl PostFields for Post { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_title(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Query; impl QueryFields for Query { fn field_noop(&self, executor: &Executor) -> FieldResult<&bool> { unimplemented!() } } pub struct Mutation; impl MutationFields for Mutation { fn field_create_post( &self, executor: &Executor, trail: &QueryTrail, input: CreatePost, ) -> FieldResult> { let title: String = input.title; unimplemented!() } } ================================================ FILE: juniper-from-schema/examples/interface.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query } type Query { search(query: String!): [SearchResult!]! @juniper(ownership: "owned") } interface SearchResult { id: ID! text: String! } type Article implements SearchResult { id: ID! text: String! } type Tweet implements SearchResult { id: ID! text: String! } } pub struct Context; impl juniper::Context for Context {} pub struct Article { id: ID, text: String, } impl ArticleFields for Article { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_text(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Tweet { id: ID, text: String, } impl TweetFields for Tweet { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_text(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Query; impl QueryFields for Query { fn field_search( &self, executor: &Executor, trail: &QueryTrail, query: String, ) -> FieldResult> { let article: Article = Article { id: ID::new("1"), text: "Business".to_string(), }; let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string(), }; let posts = vec![SearchResult::from(article), SearchResult::from(tweet)]; Ok(posts) } } fn main() {} ================================================ FILE: juniper-from-schema/examples/query_trails.rs ================================================ #![allow(clippy::redundant_pattern_matching)] #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; fn main() {} pub struct Context; impl juniper::Context for Context {} graphql_schema! { schema { query: Query } type Query { allPosts: [Post!]! @juniper(ownership: "owned") } type Post { id: Int! author: User! } type User { id: Int! } } pub struct Query; impl QueryFields for Query { fn field_all_posts( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { // Check if the query includes the author if let Some(_) = trail.author().walk() { // Somehow preload the users to avoid N+1 query bugs // Exactly how to do this depends on your setup } // Normally this would come from the database let post = Post { id: 1, author: User { id: 1 }, }; Ok(vec![post]) } } pub struct Post { id: i32, author: User, } impl PostFields for Post { fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { Ok(&self.id) } fn field_author( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult<&User> { Ok(&self.author) } } pub struct User { id: i32, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { Ok(&self.id) } } ================================================ FILE: juniper-from-schema/examples/subscription.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use async_trait::async_trait; use futures::stream::{Stream, StreamExt}; use juniper::*; use juniper_from_schema::graphql_schema; use std::pin::Pin; use tokio::sync::broadcast::Sender; graphql_schema! { schema { query: Query subscription: Subscription } type Query { // Query must have at least one field ping: Boolean! } type Subscription { tweets: Tweet! @juniper(ownership: "owned", infallible: true, stream_item_infallible: false) } type Tweet { id: ID! } } pub struct Context { tx: Sender, } impl juniper::Context for Context {} #[derive(Clone)] pub struct Tweet { id: ID, text: String, } impl TweetFields for Tweet { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } } pub struct Subscription; impl SubscriptionFields for Subscription { fn field_tweets( &self, executor: &Executor, _: &QueryTrail, ) -> Pin> + Send>> { let ctx = executor.context(); let receiver = ctx.tx.subscribe(); let stream = receiver.into_stream().map(|item| item.map_err(From::from)); Box::pin(stream) } } pub struct Query; #[async_trait] impl QueryFields for Query { fn field_ping(&self, executor: &Executor) -> FieldResult<&bool> { todo!() } } fn main() {} ================================================ FILE: juniper-from-schema/examples/union_types.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] use juniper::*; use juniper_from_schema::graphql_schema; graphql_schema! { schema { query: Query } type Query { search(query: String!): [SearchResult!]! @juniper(ownership: "owned") } union SearchResult = Article | Tweet type Article { id: ID! text: String! } type Tweet { id: ID! text: String! } } pub struct Context; impl juniper::Context for Context {} pub struct Article { id: ID, text: String, } impl ArticleFields for Article { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_text(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Tweet { id: ID, text: String, } impl TweetFields for Tweet { fn field_id(&self, executor: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_text(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } pub struct Query; impl QueryFields for Query { fn field_search( &self, executor: &Executor, trail: &QueryTrail, query: String, ) -> FieldResult> { let article: Article = Article { id: ID::new("1"), text: "Business".to_string(), }; let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string(), }; let posts = vec![SearchResult::from(article), SearchResult::from(tweet)]; Ok(posts) } } fn main() {} ================================================ FILE: juniper-from-schema/src/lib.rs ================================================ //! This library contains a procedural macro that reads a GraphQL schema file, and generates the //! corresponding [Juniper](https://crates.io/crates/juniper) [macro calls]. This means you can //! have a real schema file and be guaranteed that it matches your Rust implementation. It also //! removes most of the boilerplate involved in using Juniper. //! //! [macro calls]: https://graphql-rust.github.io/types/objects/complex_fields.html //! //! # Table of contents //! //! - [Example](#example) //! - [Example web app](#example-web-app) //! - [Supported juniper versions](#supported-juniper-versions) //! - [GraphQL features](#graphql-features) //! - [The `ID` type](#the-id-type) //! - [Custom scalar types](#custom-scalar-types) //! - [Special case scalars](#special-case-scalars) //! - [Interfaces](#interfaces) //! - [Union types](#union-types) //! - [Input objects](#input-objects) //! - [Enumeration types](#enumeration-types) //! - [Default argument values](#default-argument-values) //! - [Subscriptions](#subscriptions) //! - [Supported schema directives](#supported-schema-directives) //! - [Definition for `@juniper`](#definition-for-juniper) //! - [Customizing ownership](#customizing-ownership) //! - [Infallible fields](#infallible-fields) //! - [Async resolvers](#async-resolvers) //! - [GraphQL to Rust types](#graphql-to-rust-types) //! - [Query trails](#query-trails) //! - [Abbreviated example](#abbreviated-example) //! - [Types](#types) //! - [Downcasting for interface and union `QueryTrail`s](#downcasting-for-interface-and-union-querytrails) //! - [`QueryTrail`s for fields that take arguments](#querytrails-for-fields-that-take-arguments) //! - [Customizing the error type](#customizing-the-error-type) //! - [Customizing the context type](#customizing-the-context-type) //! - [Inspecting the generated code](#inspecting-the-generated-code) //! - [Generating code in "build.rs"](#generating-code-in-buildrs) //! //! # Example //! //! Schema: //! //! ```graphql //! schema { //! query: Query //! mutation: Mutation //! } //! //! type Query { //! // The directive makes the return value `FieldResult` //! // rather than the default `FieldResult<&String>` //! helloWorld(name: String!): String! @juniper(ownership: "owned") //! } //! //! type Mutation { //! noop: Boolean! //! } //! ``` //! //! How you could implement that schema: //! //! ``` //! #[macro_use] //! extern crate juniper; //! //! use juniper_from_schema::graphql_schema_from_file; //! //! // This is the important line //! graphql_schema_from_file!("tests/schemas/doc_schema.graphql"); //! //! pub struct Context; //! impl juniper::Context for Context {} //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_hello_world( //! &self, //! executor: &juniper::Executor, //! name: String, //! ) -> juniper::FieldResult { //! Ok(format!("Hello, {}!", name)) //! } //! } //! //! pub struct Mutation; //! //! impl MutationFields for Mutation { //! fn field_noop(&self, executor: &juniper::Executor) -> juniper::FieldResult<&bool> { //! Ok(&true) //! } //! } //! //! fn main() { //! let ctx = Context; //! //! let query = "query { helloWorld(name: \"Ferris\") }"; //! //! let (result, errors) = juniper::execute_sync( //! query, //! None, //! &Schema::new(Query, Mutation, juniper::EmptySubscription::new()), //! &juniper::Variables::new(), //! &ctx, //! ) //! .unwrap(); //! //! assert_eq!(errors.len(), 0); //! assert_eq!( //! result //! .as_object_value() //! .unwrap() //! .get_field_value("helloWorld") //! .unwrap() //! .as_scalar_value::() //! .unwrap(), //! "Hello, Ferris!", //! ); //! } //! ``` //! //! # Example web app //! //! You can find an example of how to use this library together with [Rocket] and [Diesel] to make //! a GraphQL web app at or an example of how //! to use this library with [Actix] and [Diesel] at //! . //! //! [Rocket]: https://rocket.rs //! [Diesel]: http://diesel.rs //! [Actix]: https://actix.rs/ //! //! # Supported juniper versions //! //! | juniper-from-schema | juniper | //! |---|---| //! | 0.6.x | 0.15.x | //! | 0.5.x | 0.14.x | //! //! # GraphQL features //! //! The goal of this library is to support as much of GraphQL as Juniper does. //! //! Here is the complete list of features: //! //! Supported: //! - Object types including converting lists and non-nulls to Rust types //! - Custom scalar types including the `ID` type //! - Interfaces //! - Unions //! - Input objects //! - Enumeration types //! - Async resolvers //! - Subscriptions //! //! Not supported: //! - Type extensions //! //! ## The `ID` type //! //! The `ID` GraphQL type will be generated into [`juniper::ID`]. //! //! [`juniper::ID`]: https://docs.rs/juniper/latest/juniper/struct.ID.html //! //! ## Custom scalar types //! //! Custom scalar types will be generated into a newtype wrapper around a `String`. For example: //! //! ```graphql //! scalar Cursor //! ``` //! //! Would result in //! //! ``` //! pub struct Cursor(pub String); //! ``` //! //! ## Special case scalars //! //! A couple of scalar names have special meaning. Those are: //! //! - `Url` becomes //! [`url::Url`](https://docs.rs/url/2.1.0/url/struct.Url.html). //! - `Uuid` becomes //! [`uuid::Uuid`](https://docs.rs/uuid/0.7.4/uuid/struct.Uuid.html). //! - `Date` becomes //! [`chrono::naive::NaiveDate`](https://docs.rs/chrono/0.4.6/chrono/naive/struct.NaiveDate.html). //! - `DateTimeUtc` becomes [`chrono::DateTime`] by default but if defined with //! `scalar DateTimeUtc @juniper(with_time_zone: false)` it will become [`chrono::naive::NaiveDateTime`]. //! //! Juniper doesn't support [`chrono::Date`](https://docs.rs/chrono/0.4.9/chrono/struct.Date.html) //! so therefore this library cannot support that either. You can read about Juniper's supported //! integrations [here](https://docs.rs/juniper/0.13.1/juniper/integrations/index.html). //! //! [`chrono::DateTime`]: https://docs.rs/chrono/0.4.9/chrono/struct.DateTime.html //! [`chrono::naive::NaiveDateTime`]: https://docs.rs/chrono/0.4.9/chrono/naive/struct.NaiveDateTime.html //! //! ## Interfaces //! //! Juniper has several ways of representing GraphQL interfaces in Rust. They are listed //! [here](https://graphql-rust.github.io/juniper/master/types/interfaces.html) along with their //! advantages and disadvantages. //! //! For the generated code we use the "enum value" pattern because we found it to be the most flexible. //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/interface.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Article { id: ID, text: String } //! # impl ArticleFields for Article { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # fn field_text( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&String> { unimplemented!() } //! # } //! # pub struct Tweet { id: ID, text: String } //! # impl TweetFields for Tweet { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # fn field_text( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&String> { unimplemented!() } //! # } //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! search(query: String!): [SearchResult!]! @juniper(ownership: "owned") //! } //! //! interface SearchResult { //! id: ID! //! text: String! //! } //! //! type Article implements SearchResult { //! id: ID! //! text: String! //! } //! //! type Tweet implements SearchResult { //! id: ID! //! text: String! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_search( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! query: String, //! ) -> FieldResult> { //! let article: Article = Article { id: ID::new("1"), text: "Business".to_string() }; //! let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string() }; //! //! let posts = vec![ //! SearchResult::from(article), //! SearchResult::from(tweet), //! ]; //! //! Ok(posts) //! } //! } //! ``` //! //! The enum that gets generated has variants for each type that implements the interface and also //! implements `From` for each type. //! //! ## Union types //! //! Union types are basically just interfaces so they work in very much the same way. //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/union_types.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Article { id: ID, text: String } //! # impl ArticleFields for Article { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # fn field_text( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&String> { unimplemented!() } //! # } //! # pub struct Tweet { id: ID, text: String } //! # impl TweetFields for Tweet { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # fn field_text( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&String> { unimplemented!() } //! # } //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! search(query: String!): [SearchResult!]! @juniper(ownership: "owned") //! } //! //! union SearchResult = Article | Tweet //! //! type Article { //! id: ID! //! text: String! //! } //! //! type Tweet { //! id: ID! //! text: String! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_search( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! query: String, //! ) -> FieldResult> { //! let article: Article = Article { id: ID::new("1"), text: "Business".to_string() }; //! let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string() }; //! //! let posts = vec![ //! SearchResult::from(article), //! SearchResult::from(tweet), //! ]; //! //! Ok(posts) //! } //! } //! ``` //! //! ## Input objects //! //! Input objects will be converted into Rust structs with public fields. //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/input_types.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Post { id: ID } //! # impl PostFields for Post { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { //! # unimplemented!() //! # } //! # fn field_title( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&String> { //! # unimplemented!() //! # } //! # } //! # pub struct Query; //! # impl QueryFields for Query { //! # fn field_noop( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&bool> { //! # unimplemented!() //! # } //! # } //! graphql_schema! { //! schema { //! query: Query //! mutation: Mutation //! } //! //! type Mutation { //! createPost(input: CreatePost!): Post @juniper(ownership: "owned") //! } //! //! input CreatePost { //! title: String! //! } //! //! type Post { //! id: ID! //! title: String! //! } //! //! type Query { noop: Boolean! } //! } //! //! pub struct Mutation; //! //! impl MutationFields for Mutation { //! fn field_create_post( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! input: CreatePost, //! ) -> FieldResult> { //! let title: String = input.title; //! //! unimplemented!() //! } //! } //! ``` //! //! From that example `CreatePost` will be defined as //! //! ``` //! pub struct CreatePost { //! pub title: String, //! } //! ``` //! //! ## Enumeration types //! //! GraphQL enumeration types will be converted into normal Rust enums. The name of each variant //! will be camel cased. //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/enumeration_types.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Post { id: ID } //! # impl PostFields for Post { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { //! # Ok(&self.id) //! # } //! # } //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! enum Status { //! PUBLISHED //! UNPUBLISHED //! } //! //! type Query { //! allPosts(status: Status!): [Post!]! @juniper(ownership: "owned") //! } //! //! type Post { //! id: ID! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_all_posts( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! status: Status, //! ) -> FieldResult> { //! match status { //! Status::Published => unimplemented!("find published posts"), //! Status::Unpublished => unimplemented!("find unpublished posts"), //! } //! } //! } //! ``` //! //! ## Default argument values //! //! In GraphQL you are able to provide default values for field arguments, provided the argument is //! nullable. //! //! Arguments of the following types support default values: //! - `Float` //! - `Int` //! - `String` //! - `Boolean` //! - Enumerations //! - Input objects (as field arguments, see below) //! - Lists containing some other supported type //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/default_argument_values.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Post { id: ID } //! # impl PostFields for Post { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { //! # Ok(&self.id) //! # } //! # } //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! enum Status { //! PUBLISHED //! UNPUBLISHED //! } //! //! input Pagination { //! pageSize: Int! //! cursor: ID //! } //! //! type Query { //! allPosts( //! status: Status = PUBLISHED, //! pagination: Pagination = { pageSize: 20 } //! ): [Post!]! @juniper(ownership: "owned") //! } //! //! type Post { //! id: ID! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_all_posts( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! status: Status, //! pagination: Pagination, //! ) -> FieldResult> { //! // `status` will be `Status::Published` if not given in the query //! //! match status { //! Status::Published => unimplemented!("find published posts"), //! Status::Unpublished => unimplemented!("find unpublished posts"), //! } //! } //! } //! ``` //! //! ### Input object gotchas //! //! Defaults for input objects are only supported as field arguments. The following is not //! supported //! //! ```graphql //! input SomeType { //! field: Int = 1 //! } //! ``` //! //! This isn't supported because [the spec is unclear about how to handle multiple nested //! defaults](https://github.com/webonyx/graphql-php/issues/350). //! //! Also, defaults are only used if no arguments are passed. So given the schema //! //! ```graphql //! input Input { //! a: String //! b: String //! } //! //! type Query { //! field(arg: Input = { a: "a" }): Int! //! } //! ``` //! //! and the query //! //! ```graphql //! query MyQuery { //! field(arg: { b: "my b" }) //! } //! ``` //! //! The value of `arg` inside the resolver would be `Input { a: None, b: Some("my b") }`. Note that //! even though `a` has a default value in the field doesn't get used here because we set `arg` in //! the query. //! //! ## Subscriptions //! //! Subscriptions work differently from queries and mutations. Rather than completing a request by //! sending just one result you instead receive a stream of results. The server can then publish //! new results to the clients through the stream. The [juniper //! book](https://graphql-rust.github.io/juniper/master/advanced/subscriptions.html) has more //! details. //! //! The return type of your subscription resolvers must always return something that implements //! [`futures::stream::Stream`]. By default that will be `Pin + Send>>`. //! //! [`futures::stream::Stream`]: https://docs.rs/futures/0.3.6/futures/stream/trait.Stream.html //! //! Abbreviated example (find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/subscription.rs)): //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Query; //! # impl QueryFields for Query { //! # fn field_ping(&self, executor: &Executor) -> FieldResult<&bool> { //! # todo!() //! # } //! # } //! use futures::stream::Stream; //! use std::pin::Pin; //! //! graphql_schema! { //! schema { //! query: Query //! subscription: Subscription //! } //! //! type Query { //! // Query must have at least one field //! ping: Boolean! //! } //! //! type Subscription { //! idsOfNewThings: ID! @juniper(ownership: "owned", infallible: true) //! } //! } //! //! pub struct Subscription; //! //! impl SubscriptionFields for Subscription { //! fn field_ids_of_new_things( //! &self, //! executor: &Executor, //! ) -> Pin + Send>> { //! // `futures::stream::iter` creates a stream out of any iterator //! // this is useful for demonstration //! Box::pin(futures::stream::iter(vec![ //! ID::new("1"), //! ID::new("2"), //! ID::new("3"), //! ])) //! } //! } //! ``` //! //! ### Customizing the stream type //! //! Using `Pin + Send>>` as your stream type is nice for most use //! cases. However if you want to save the allocation and have a concrete stream type you can //! change the type with `@juniper(stream_type: "IdStream")`. //! //! Abbreviated example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Query; //! # impl QueryFields for Query { //! # fn field_ping(&self, executor: &Executor) -> FieldResult<&bool> { //! # todo!() //! # } //! # } //! use futures::stream::Stream; //! //! graphql_schema! { //! schema { //! query: Query //! subscription: Subscription //! } //! //! type Query { //! // Query must have at least one field //! ping: Boolean! //! } //! //! type Subscription { //! idsOfNewThings: ID! @juniper(stream_type: "IdStream", infallible: true, ownership: "owned") //! } //! } //! //! pub struct Subscription; //! //! impl SubscriptionFields for Subscription { //! fn field_ids_of_new_things( //! &self, //! executor: &Executor, //! ) -> IdStream { //! IdStream //! } //! } //! //! pub struct IdStream; //! //! impl Stream for IdStream { //! type Item = ID; //! //! fn poll_next( //! self: std::pin::Pin<&mut Self>, //! cx: &mut futures::task::Context<'_>, //! ) -> futures::task::Poll> { //! // your implementation here //! # todo!() //! } //! } //! ``` //! //! ### Interactions with `@juniper` directives //! //! There are a few things to keep in mind that are specific to subscriptions and the `@juniper` //! directive. //! //! Consider you have a field with `@juniper(infallible: false)`. Does that mean the resolver that //! produces the stream can fail or does it mean that the stream itself produces `Result`s? //! //! In juniper-from-schema we've chosen at `infallible`, `ownership`, and `async` applies to the //! resolver that produces the stream. This is to remain consistent with the rest of the library. //! //! If you actually want a stream of `Result`s you can use the `@juniper(stream_item_infallible: //! false)` directive. //! //! By default `stream_item_infallible` is `true` meaning your stream doesn't produce `Result`s but //! instead successful values. //! //! Abbreviated example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Query; //! # impl QueryFields for Query { //! # fn field_ping(&self, executor: &Executor) -> FieldResult<&bool> { //! # todo!() //! # } //! # } //! use futures::stream::Stream; //! //! graphql_schema! { //! schema { //! query: Query //! subscription: Subscription //! } //! //! type Query { //! // Query must have at least one field //! ping: Boolean! //! } //! //! type Subscription { //! idsOfNewThings: ID! @juniper( //! // stream is fallible, so it produces `Result`s //! stream_item_infallible: false, //! // type of our stream //! stream_type: "IdStream", //! // creating the stream itself can fail //! infallible: false, //! // the stream we produce is owned //! ownership: "owned" //! ) //! } //! } //! //! pub struct Subscription; //! //! impl SubscriptionFields for Subscription { //! fn field_ids_of_new_things( //! &self, //! executor: &Executor, //! ) -> FieldResult { //! Ok(IdStream) //! } //! } //! //! pub struct IdStream; //! //! impl Stream for IdStream { //! type Item = FieldResult; //! //! fn poll_next( //! self: std::pin::Pin<&mut Self>, //! cx: &mut futures::task::Context<'_>, //! ) -> futures::task::Poll> { //! // your implementation here //! # todo!() //! } //! } //! ``` //! //! Something like `@juniper(stream_item_ownership: "borrowed")` is not supported and all streams //! must therefore produce owned values. //! //! Your stream resolvers are also required to use `@juniper(ownership: "owned")`. `"as_ref"` or //! `"borrowed"` are not supported: //! //! ```compile_fail //! # #[macro_use] //! # extern crate juniper; //! # use std::pin::Pin; //! # use juniper::*; //! # use juniper::futures::stream::Stream; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Query; //! # impl QueryFields for Query { //! # fn field_ping(&self, executor: &Executor) -> FieldResult<&bool> { //! # todo!() //! # } //! # } //! use futures::stream::Stream; //! //! graphql_schema! { //! schema { //! query: Query //! subscription: Subscription //! } //! //! type Query { //! // Query must have at least one field //! ping: Boolean! //! } //! //! type Subscription { //! // "as_ref" is not supported //! asRefNotSupported: [ID!]! @juniper(infallible: true, ownership: "as_ref") //! //! // neither is "borrowed" //! borrowedNotSupported: ID! @juniper(infallible: true, ownership: "borrowed") //! } //! } //! //! pub struct Subscription; //! //! impl SubscriptionFields for Subscription { //! fn field_as_ref_not_supported( //! &self, //! executor: &Executor, //! ) -> Pin> + Send>> { //! // ... //! # todo!() //! } //! //! fn field_borrowed_not_supported( //! &self, //! executor: &Executor, //! ) -> Pin + Send>> { //! // ... //! # todo!() //! } //! } //! ``` //! //! # Supported schema directives //! //! A number of [schema directives][] are supported that lets you customize the generated code: //! //! - `@juniper(ownership: "owned|borrowed|as_ref")`. For customizing ownership of returned data. //! More info [here](#customizing-ownership). //! - `@juniper(infallible: true|false)`. Customize if a field should return `Result` or //! just `T`. More info //! [here](http://localhost:4000/juniper_from_schema/index.html#infallible-fields). //! - `@juniper(async: true|false)`. For choosing whether your resolver function should be sync or //! async. The default is sync. More info [here](#async-resolvers). //! - `@juniper(stream_item_infallible: true|false)`. For choosing whether the stream produces //! `Result`s or plain values. Default is `true` meaning the stream does not produce `Result`s. //! - `@deprecated`. For deprecating types in your schema. Also supports supplying a reason with //! `@deprecated(reason: "...")` //! //! [schema directives]: https://www.apollographql.com/docs/apollo-server/schema/directives/ //! //! ## Definition for `@juniper` //! //! Some tools that operate on your GraphQL schema require you to include the definition for all //! directives used. So in case you need it the definition for `@juniper` is: //! //! ```graphql //! directive @juniper( //! ownership: String = "borrowed", //! infallible: Boolean = false, //! with_time_zone: Boolean = true, //! async: Boolean = false, //! stream_item_infallible: Boolean = true, //! stream_type: String = null //! ) on FIELD_DEFINITION | SCALAR //! ``` //! //! This directive definition is allowed in your schema, as well as any other directive definition. //! Definitions of `@juniper` that differ from this are not allowed though. //! //! The definition might change in future versions. Please refer to the [changelog][]. //! //! juniper-from-schema doesn't require to put this in your schema, so you only need to include it //! if some other tool requires it. //! //! [changelog]: https://github.com/davidpdrsn/juniper-from-schema/blob/master/CHANGELOG.md //! //! ## Customizing ownership //! //! By default all fields return borrowed values. Specifically the type is //! `juniper::FieldResult<&'a T>` where `'a` is the lifetime of `self`. This works well for //! returning data owned by `self` and avoids needless `.clone()` calls you would need if fields //! returned owned values. //! //! However if you need to change the ownership you have to add the directive //! `@juniper(ownership:)` to the field in the schema. //! //! It takes the following arguments: //! //! - `@juniper(ownership: "borrowed")`: The data returned will be borrowed from `self` //! (`FieldResult<&T>`). //! - `@juniper(ownership: "owned")`: The return type will be owned (`FieldResult`). //! - `@juniper(ownership: "as_ref")`: Only applicable for `Option` and `Vec` return types. Changes //! the inner type to be borrowed (`FieldResult>` or `FieldResult>`). //! //! Note that fields in subscription types must use `@juniper(ownership: "owned")`. `"as_ref"` or //! `"borrowed"` are not supported. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper_from_schema::*; //! # use juniper::*; //! # pub struct Context; //! # impl juniper::Context for Context {} //! # fn main() {} //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! borrowed: String! //! owned: String! @juniper(ownership: "owned") //! asRef: String @juniper(ownership: "as_ref") //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_borrowed(&self, _: &Executor) -> FieldResult<&String> { //! // ... //! # unimplemented!() //! } //! //! fn field_owned(&self, _: &Executor) -> FieldResult { //! // ... //! # unimplemented!() //! } //! //! fn field_as_ref(&self, _: &Executor) -> FieldResult> { //! // ... //! # unimplemented!() //! } //! } //! ``` //! //! All field arguments will be owned. //! //! ## Infallible fields //! //! By default the generated resolvers are fallible, meaining they return a `Result` rather //! than a bare `T`. You can customize that using `@juniper(infallible: true)`. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper_from_schema::*; //! # use juniper::*; //! # pub struct Context; //! # impl juniper::Context for Context {} //! # fn main() {} //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! canError: String! //! cannotError: String! @juniper(infallible: true) //! cannotErrorAndOwned: String! @juniper(infallible: true, ownership: "owned") //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_can_error(&self, _: &Executor) -> FieldResult<&String> { //! // ... //! # unimplemented!() //! } //! //! fn field_cannot_error(&self, _: &Executor) -> &String { //! // ... //! # unimplemented!() //! } //! //! fn field_cannot_error_and_owned(&self, _: &Executor) -> String { //! // ... //! # unimplemented!() //! } //! } //! ``` //! //! ## Async resolvers //! //! By default the generated resolvers are synchronous. If you want an async resolver instead you //! can change it with `@juniper(async: true)`. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper_from_schema::*; //! # use juniper::*; //! # pub struct Context; //! # impl juniper::Context for Context {} //! # fn main() {} //! // `async` methods are currently not supported in traits. So we use "async_trait" to make them //! // work. "async_trait" is also used by juniper under the covers. //! use async_trait::async_trait; //! //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! findUser(id: ID!): User! @juniper(async: true, ownership: "owned") //! //! // async resolvers also support `ownership: "as_ref"` //! allUsers: [User!]! @juniper(async: true, ownership: "as_ref") //! } //! //! type User { //! id: ID! @juniper(infallible: true) //! } //! } //! //! pub struct Query; //! //! #[async_trait] //! impl QueryFields for Query { //! // Async resolvers are required to specify the lifetimes 's, 'r, and 'a because of how //! // "async_trait" works //! async fn field_find_user<'s, 'r, 'a>( //! &'s self, //! _: &Executor<'r, 'a, Context>, //! _: &QueryTrail<'r, User, Walked>, //! id: ID, //! ) -> FieldResult { //! // ... //! # unimplemented!() //! } //! //! // Use `'s` to return data borrowed from `self` //! async fn field_all_users<'s, 'r, 'a>( //! &'s self, //! _: &Executor<'r, 'a, Context>, //! _: &QueryTrail<'r, User, Walked>, //! ) -> FieldResult> { //! // ... //! # unimplemented!() //! } //! } //! //! pub struct User { //! id: ID, //! } //! //! impl UserFields for User { //! fn field_id(&self, _: &Executor) -> &ID { //! &self.id //! } //! } //! ``` //! //! # GraphQL to Rust types //! //! This is how the standard GraphQL types will be mapped to Rust: //! //! - `Int` -> `i32` //! - `Float` -> `f64` //! - `String` -> `String` //! - `Boolean` -> `bool` //! - `ID` -> [`juniper::ID`](https://docs.rs/juniper/latest/juniper/struct.ID.html) //! //! # Query trails //! //! If you're not careful about preloading associations for deeply nested queries you risk getting //! lots of [N+1 query bugs][]. Juniper provides a [look ahead API][] which lets you inspect things //! coming up further down a query. However the API is string based, so you risk making typos and //! checking for fields that don't exist. //! //! `QueryTrail` is a thin wrapper around Juniper look aheads with generated methods for each field //! on all your types. This means the compiler will reject your code if you're checking for invalid //! fields. //! //! Resolver methods (`field_*`) that return object types (non scalar values) will also get a //! `QueryTrail` argument besides the executor. //! //! Since the `QueryTrail` type itself is defined in this crate (rather than being inserted into //! your code) we cannot directly add methods for your GraphQL fields. Those methods have to be //! added through ["extension traits"](http://xion.io/post/code/rust-extension-traits.html). So if //! you see an error like //! //! ```text //! | trail.foo(); //! | ^^^ method not found in `&juniper_from_schema::QueryTrail<'r, User, juniper_from_schema::Walked>` //! | //! = help: items from traits can only be used if the trait is in scope //! help: the following trait is implemented but not in scope, perhaps add a `use` for it: //! | //! 2 | use crate::graphql_schema::query_trails::QueryTrailUserExtensions; //! | //! ``` //! //! Then adding `use crate::graphql_schema::query_trails::*` to you module should fix it. This is //! necessary because all the extention traits are generated inside a module called `query_trails`. //! This is done so you can glob import the `QueryTrail` extension traits without glob importing //! everything from your GraphQL schema. //! //! If you just want everything from the schema `use crate::graphql_schema::*` will also bring in //! the extension traits. //! //! [N+1 query bugs]: https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/ //! [look ahead API]: https://docs.rs/juniper/0.11.1/juniper/struct.LookAheadSelection.html //! //! ## Abbreviated example //! //! Find [complete example here](https://github.com/davidpdrsn/juniper-from-schema/blob/master/examples/query_trails.rs) //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! allPosts: [Post!]! @juniper(ownership: "owned") //! } //! //! type Post { //! id: Int! //! author: User! //! } //! //! type User { //! id: Int! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_all_posts( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! ) -> FieldResult> { //! // Check if the query includes the author //! if let Some(_) = trail.author().walk() { //! // Somehow preload the users to avoid N+1 query bugs //! // Exactly how to do this depends on your setup //! } //! //! // Normally this would come from the database //! let post = Post { //! id: 1, //! author: User { id: 1 }, //! }; //! //! Ok(vec![post]) //! } //! } //! //! pub struct Post { //! id: i32, //! author: User, //! } //! //! impl PostFields for Post { //! fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { //! Ok(&self.id) //! } //! //! fn field_author( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! ) -> FieldResult<&User> { //! Ok(&self.author) //! } //! } //! //! pub struct User { //! id: i32, //! } //! //! impl UserFields for User { //! fn field_id( //! &self, //! executor: &Executor, //! ) -> FieldResult<&i32> { //! Ok(&self.id) //! } //! } //! ``` //! //! ## Types //! //! A query trail has two generic parameters: `QueryTrail<'r, T, K>`. `T` is the type the current //! field returns and `K` is either `Walked` or `NotWalked`. //! //! The lifetime `'r` comes from Juniper and is the lifetime of the incoming query. //! //! ### `T` //! //! The `T` allows us to implement different methods for different types. For example in the //! example above we implement `id` and `author` for `QueryTrail<'r, Post, K>` but only `id` for //! `QueryTrail<'r, User, K>`. //! //! If your field returns a `Vec` or `Option` the given query trail will be `QueryTrail<'r, //! T, _>`. So `Vec` or `Option` will be removed and you'll only be given the inner most type. //! That is because in the GraphQL query syntax it doesn't matter if you're querying a `User` //! or `[User]`. The fields you have access to are the same. //! //! ### `K` //! //! The `Walked` and `NotWalked` types are used to check if a given trail has been checked to //! actually be part of a query. Calling any method on a `QueryTrail<'r, T, K>` will return //! `QueryTrail<'r, T, NotWalked>`, and to check if the trail is actually part of the query you have //! to call `.walk()` which returns `Option>`. If that is a `Some(_)` //! you'll know the trail is part of the query and you can do whatever preloading is necessary. //! //! Example: //! //! ```ignore //! if let Some(walked_trail) = trail //! .some_field() //! .some_other_field() //! .third_field() //! .walk() //! { //! // preload stuff //! } //! ``` //! //! You can always run `cargo doc` and inspect all the methods on `QueryTrail` and in which //! contexts you can call them. //! //! ## Downcasting for interface and union `QueryTrail`s //! //! _This section is mostly relevant if you're using //! [juniper-eager-loading](https://crates.io/crates/juniper-eager-loading) however it isn't //! specific to that library._ //! //! If you have a `QueryTrail<'r, T, Walked>` where `T` is an interface or union type you can use //! `.downcast()` to convert that `QueryTrail` into one of the implementors of the interface or //! union. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Article { id: ID } //! # impl ArticleFields for Article { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # } //! # pub struct Tweet { id: ID, text: String } //! # impl TweetFields for Tweet { //! # fn field_id( //! # &self, //! # executor: &Executor, //! # ) -> FieldResult<&ID> { unimplemented!() } //! # } //! # //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! search(query: String!): [SearchResult!]! //! } //! //! interface SearchResult { //! id: ID! //! } //! //! type Article implements SearchResult { //! id: ID! //! } //! //! type Tweet implements SearchResult { //! id: ID! //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_search( //! &self, //! executor: &Executor, //! trail: &QueryTrail, //! query: String, //! ) -> FieldResult<&Vec> { //! let article_trail: QueryTrail = trail.downcast(); //! let tweet_trail: QueryTrail = trail.downcast(); //! //! // ... //! # unimplemented!() //! } //! } //! ``` //! //! ### Why is this useful? //! //! If you were do perform some kind of preloading of data you might have a function that inspects //! a `QueryTrail` and loads the necessary data from a database. Such a function could look like //! this: //! //! ```ignore //! fn preload_users( //! mut users: Vec, //! query_trail: &QueryTrail, //! db: &Database, //! ) -> Vec { //! // ... //! } //! ``` //! //! This function works well when we have field that returns `[User!]!`. That field is going to get //! a `QueryTrail<'r, User, Walked>` which is exactly what `preload_users` needs. //! //! However, now imagine you have a schema like this: //! //! ```graphql //! type Query { //! search(query: String!): [SearchResult!]! //! } //! //! union SearchResult = User | City | Country //! //! type User { //! id: ID! //! city: City! //! } //! //! type City { //! id: ID! //! country: Country! //! } //! //! type Country { //! id: ID! //! } //! ``` //! //! The method `QueryFields::field_search` will receive a `QueryTrail<'r, SearchResult, Walked>`. //! That type doesn't work with `preload_users`. So we have to convert our `QueryTrail<'r, //! SearchResult, Walked>` into `QueryTrail<'r, User, Walked>`. //! //! This can be done by calling `.downcast()` which automatically gets implemented for interface and //! union query trails. See above for an example. //! //! ## `QueryTrail`s for fields that take arguments //! //! Sometimes you have GraphQL fields that take arguments that impact which things your resolvers //! should return. `QueryTrail` therefore also allows you inspect arguments to fields. //! //! Abbreviated example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper_from_schema::*; //! # pub struct Context; //! # impl juniper::Context for Context {} //! # fn main() {} //! # pub struct Country {} //! # impl CountryFields for Country { //! # fn field_users<'r, 'a>( //! # &self, //! # executor: &juniper::Executor<'r, 'a, Context>, //! # trail: &QueryTrail<'r, User, Walked>, //! # active_since: DateTime, //! # ) -> juniper::FieldResult> { //! # unimplemented!() //! # } //! # } //! # pub struct User {} //! # impl UserFields for User { //! # fn field_id<'r, 'a>( //! # &self, //! # executor: &juniper::Executor<'r, 'a, Context>, //! # ) -> juniper::FieldResult<&juniper::ID> { //! # unimplemented!() //! # } //! # } //! use chrono::prelude::*; //! //! graphql_schema! { //! schema { //! query: Query //! } //! //! type Query { //! countries: [Country!]! @juniper(ownership: "owned") //! } //! //! type Country { //! users(activeSince: DateTimeUtc!): [User!]! @juniper(ownership: "owned") //! } //! //! type User { //! id: ID! //! } //! //! scalar DateTimeUtc //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_countries<'r, 'a>( //! &self, //! executor: &'a juniper::Executor<'r, 'a, Context>, //! trail: &'a QueryTrail<'r, Country, Walked> //! ) -> juniper::FieldResult> { //! // Get struct that has all arguments passed to `Country.users` //! let args: CountryUsersArgs<'a> = trail.users_args(); //! //! // The struct has methods for each argument, e.g. `active_since`. //! // //! // Notice that it automatically converts the incoming value to //! // a `DateTime`. //! let _: DateTime = args.active_since(); //! //! # unimplemented!() //! // ... //! } //! } //! ``` //! //! You can also elide the `'a` lifetime: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper_from_schema::*; //! # pub struct Context; //! # impl juniper::Context for Context {} //! # fn main() {} //! # pub struct Country {} //! # impl CountryFields for Country { //! # fn field_users( //! # &self, //! # executor: &juniper::Executor, //! # trail: &QueryTrail, //! # active_since: DateTime, //! # ) -> juniper::FieldResult> { //! # unimplemented!() //! # } //! # } //! # pub struct User {} //! # impl UserFields for User { //! # fn field_id( //! # &self, //! # executor: &juniper::Executor, //! # ) -> juniper::FieldResult<&juniper::ID> { //! # unimplemented!() //! # } //! # } //! # use chrono::prelude::*; //! # graphql_schema! { //! # schema { //! # query: Query //! # } //! # type Query { //! # countries: [Country!]! @juniper(ownership: "owned") //! # } //! # type Country { //! # users(activeSince: DateTimeUtc!): [User!]! @juniper(ownership: "owned") //! # } //! # type User { //! # id: ID! //! # } //! # scalar DateTimeUtc //! # } //! # pub struct Query; //! # //! impl QueryFields for Query { //! fn field_countries( //! &self, //! executor: &juniper::Executor, //! trail: &QueryTrail //! ) -> juniper::FieldResult> { //! let args: CountryUsersArgs = trail.users_args(); //! //! # unimplemented!() //! // ... //! } //! } //! ``` //! //! The name of the arguments struct will always be `{name of type}{name of field}Args` (e.g. //! `CountryUsersArgs`). The method names will always be the name of the arguments in snake case. //! //! The `*_args` method is only defined on `Walked` query trails so if you get an error like: //! //! ```text //! ---- src/lib.rs - (line 10) stdout ---- //! error[E0599]: no method named `users_args` found for type `&QueryTrail<'r, Country, Walked>` in the current //! scope //! --> src/lib.rs:10:1 //! | //! 10 | trail.users_args(); //! | ^^^^^^^^^^^^ method not found in `&QueryTrail<'r, Country, Walked>` //! ``` //! //! It is likely because you've forgotten to call [`.walk()`][] on `trail`. //! //! [`.walk()`]: struct.QueryTrail.html#method.walk //! //! Remember that you can always run `cargo doc` to get a high level overview of the generated //! code. //! //! # Customizing the error type //! //! By default the return type of the generated field methods will be [`juniper::FieldResult`]. //! That is just a type alias for `std::result::Result`. Should you want to //! use a different error type than [`juniper::FieldError`] that can be done by passing `, //! error_type: YourType` to [`graphql_schema_from_file!`]. //! //! Just keep in that your custom error type must implement [`juniper::IntoFieldError`] to //! type check. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema_from_file; //! # fn main() {} //! # pub struct Context; //! # impl juniper::Context for Context {} //! # pub struct Mutation; //! # impl MutationFields for Mutation { //! # fn field_noop(&self, executor: &Executor) -> Result<&bool, MyError> { //! # Ok(&true) //! # } //! # } //! graphql_schema_from_file!("tests/schemas/doc_schema.graphql", error_type: MyError); //! //! pub struct MyError(String); //! //! impl juniper::IntoFieldError for MyError { //! fn into_field_error(self) -> juniper::FieldError { //! // Perform custom error handling //! juniper::FieldError::from(self.0) //! } //! } //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_hello_world( //! &self, //! executor: &Executor, //! name: String, //! ) -> Result { //! Ok(format!("Hello, {}!", name)) //! } //! } //! ``` //! //! [`graphql_schema!`] does not support changing the error type. //! //! [`graphql_schema!`]: macro.graphql_schema.html //! [`graphql_schema_from_file!`]: macro.graphql_schema_from_file.html //! [`juniper::IntoFieldError`]: https://docs.rs/juniper/0.11.1/juniper/trait.IntoFieldError.html //! [`juniper::FieldError`]: https://docs.rs/juniper/0.11.1/juniper/struct.FieldError.html //! [`juniper::FieldResult`]: https://docs.rs/juniper/0.11.1/juniper/type.FieldResult.html //! //! # Customizing the context type //! //! By default the generate code will assume your context type is called `Context`. If that is not //! the case you can customize it by calling [`graphql_schema_from_file!`] with `context_type: NewName`. //! //! Example: //! //! ``` //! # #[macro_use] //! # extern crate juniper; //! # use juniper::*; //! # use juniper_from_schema::graphql_schema_from_file; //! # fn main() {} //! # pub struct Mutation; //! # impl MutationFields for Mutation { //! # fn field_noop(&self, executor: &Executor) -> juniper::FieldResult<&bool> { //! # Ok(&true) //! # } //! # } //! graphql_schema_from_file!("tests/schemas/doc_schema.graphql", context_type: MyContext); //! //! pub struct MyContext; //! impl juniper::Context for MyContext {} //! //! pub struct Query; //! //! impl QueryFields for Query { //! fn field_hello_world( //! &self, //! executor: &Executor, //! name: String, //! ) -> juniper::FieldResult { //! Ok(format!("Hello, {}!", name)) //! } //! } //! ``` //! //! [`graphql_schema!`] does not support changing the context type. //! //! [`graphql_schema!`]: macro.graphql_schema.html //! [`graphql_schema_from_file!`]: macro.graphql_schema_from_file.html //! //! # Inspecting the generated code //! //! If you wish to see exactly what code gets generated you can set the env var //! `JUNIPER_FROM_SCHEMA_DEBUG` to `1` when compiling. For example: //! //! ```bash //! JUNIPER_FROM_SCHEMA_DEBUG=1 cargo build //! ``` //! //! The code will not be formatted so it might be tricky to read. The easiest way to fix this is to //! copy the printed code to a file and run it through [rustfmt]. //! //! [rustfmt]: https://github.com/rust-lang/rustfmt //! //! # Generating code in "build.rs" //! //! If generating the code from a procedural macro isn't your thing you can also generate the code //! from a "build.rs" file. Add [juniper-from-schema-build] as a build dependency and call the //! appropriate function. See its docs for examples and more info. //! //! [juniper-from-schema-build]: https://crates.io/crates/juniper-from-schema-build #![deny( missing_docs, unused_imports, dead_code, unused_variables, unused_must_use )] #![doc(html_root_url = "https://docs.rs/juniper-from-schema/0.5.2")] use juniper::{DefaultScalarValue, LookAheadSelection}; use std::marker::PhantomData; // re-export juniper here so we're sure to use the same version everywhere #[doc(hidden)] pub use futures; #[doc(hidden)] pub use juniper; pub use juniper_from_schema_proc_macro::{graphql_schema, graphql_schema_from_file}; /// A type used to parameterize `QueryTrail` to know that `walk` has been called. pub struct Walked; /// A type used to parameterize `QueryTrail` to know that `walk` has *not* been called. pub struct NotWalked; /// A wrapper around a `juniper::LookAheadSelection` with methods for each possible child. pub struct QueryTrail<'r, T, K> { // These fields are required by the macros but you shouldn't rely them. They might change // without a major version increase. #[doc(hidden)] pub look_ahead: Option<&'r LookAheadSelection<'r, DefaultScalarValue>>, #[doc(hidden)] pub node_type: PhantomData, #[doc(hidden)] pub walked: K, } impl<'r, T> QueryTrail<'r, T, NotWalked> { /// Check if the trail is present in the query being executed pub fn walk(self) -> Option> { match self.look_ahead { Some(inner) => Some(QueryTrail { look_ahead: Some(inner), node_type: self.node_type, walked: Walked, }), None => None, } } } impl<'r, T, K> QueryTrail<'r, T, K> { #[allow(clippy::new_ret_no_self)] #[doc(hidden)] #[allow(missing_docs)] // This method is required by the macros but you shouldn't rely them. They might change // without a major version increase. pub fn new(lh: &'r LookAheadSelection<'r, DefaultScalarValue>) -> QueryTrail<'r, T, Walked> { QueryTrail { look_ahead: Some(lh), node_type: PhantomData, walked: Walked, } } } /// Include the code generated by "juniper-from-schema-build" in a "build.rs" file. /// /// Example: /// /// ```rust,ignore /// juniper_from_schema::include_schema!(); /// ``` #[macro_export] macro_rules! include_schema { () => { std::include!(std::concat!( std::env!("OUT_DIR"), "/juniper_from_schema_graphql_schema.rs" )); }; } #[cfg(test)] mod test { #[allow(unused_imports)] use super::*; use trybuild::TestCases; #[test] fn test_compile_pass() { let t = TestCases::new(); setup_subscription_tests("pass", &t); setup_subscription_tests("fail", &t); t.pass("tests/compile_pass/*.rs"); t.compile_fail("tests/compile_fail/*.rs"); } #[allow(dead_code)] fn setup_subscription_tests(outcome: &str, t: &TestCases) { for entry in std::fs::read_dir(format!("tests/subscriptions/{}", outcome)).unwrap() { let path = entry.unwrap().path(); let file_name = path.file_name().unwrap().to_str().unwrap(); if !file_name.contains(".rs") { continue; } match outcome { "pass" => t.pass(&format!("tests/subscriptions/{}/{}", outcome, file_name)), "fail" => t.compile_fail(&format!("tests/subscriptions/{}/{}", outcome, file_name)), other => panic!("Unsupported outcome {:?}", other), } } } } ================================================ FILE: juniper-from-schema/tests/compile_fail/docs_on_special_case_scalars.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); use url::Url; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { foo: String! @juniper(ownership: "owned") } "Url docs" scalar Url "DateTimeUtc docs" scalar DateTimeUtc "Date docs" scalar Date "Uuid docs" scalar Uuid } pub struct Query; impl QueryFields for Query { fn field_foo(&self, _: &Executor) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/docs_on_special_case_scalars.stderr ================================================ error: proc macro panicked --> $DIR/docs_on_special_case_scalars.rs:6:1 | 6 | / juniper_from_schema::graphql_schema! { 7 | | schema { 8 | | query: Query 9 | | } ... | 25 | | scalar Uuid 26 | | } | |_^ | = help: message: error: Special case scalars don't support having descriptions because the Rust types are defined in external crates --> schema:2:62 | 2 | { foo : String ! @ juniper(ownership : "owned") } "Url docs" scalar Url | ^ error: Special case scalars don't support having descriptions because the Rust types are defined in external crates --> schema:3:20 | 3 | "DateTimeUtc docs" scalar DateTimeUtc "Date docs" scalar Date "Uuid docs" | ^ error: Special case scalars don't support having descriptions because the Rust types are defined in external crates --> schema:3:51 | 3 | "DateTimeUtc docs" scalar DateTimeUtc "Date docs" scalar Date "Uuid docs" | ^ error: Special case scalars don't support having descriptions because the Rust types are defined in external crates --> schema:4:1 | 4 | scalar Uuid | ^ aborting due to 4 errors error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/docs_on_special_case_scalars.rs:30:6 | 30 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_as_ref_type.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { asRefString: String! @juniper(ownership: "as_ref") } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_as_ref_string( &self, executor: &Executor, ) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_as_ref_type.stderr ================================================ error: proc macro panicked --> $DIR/invalid_as_ref_type.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | asRefString: String! @juniper(ownership: "as_ref") 7 | | } ... | 11 | | } 12 | | } | |_^ | = help: message: error: @juniper(ownership: "as_ref") is only supported on `Option` and `Vec` types --> schema:1:14 | 1 | type Query { asRefString : String ! @ juniper(ownership : "as_ref") } schema | ^ aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/invalid_as_ref_type.rs:16:6 | 16 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_date_time_scalar_directive.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); use chrono::prelude::*; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { dateTime: DateTimeUtc! @juniper(ownership: "owned") } scalar DateTimeUtc @juniper(with_time_zone: "foobar") } pub struct Query; impl QueryFields for Query { fn field_date_time(&self, _: &Executor) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_date_time_scalar_directive.stderr ================================================ error: proc macro panicked --> $DIR/invalid_date_time_scalar_directive.rs:6:1 | 6 | / juniper_from_schema::graphql_schema! { 7 | | schema { 8 | | query: Query 9 | | } ... | 15 | | scalar DateTimeUtc @juniper(with_time_zone: "foobar") 16 | | } | |_^ | = help: message: error: Unsupported directive. --> schema:3:1 | 3 | @ juniper(with_time_zone : "foobar") | ^ Invalid type. Expected `Boolean`, got `String` aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/invalid_date_time_scalar_directive.rs:20:6 | 20 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_juniper_directive_definition.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! } schema { query: Query } directive @juniper( ownership: Boolean, infallible: String = "foo", with_time_zone: [String] = false, async: Boolean = true, stream_type: String = null, stream_item_infallible: Boolean = false, bar: [Boolean] ) on FIELD } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_juniper_directive_definition.stderr ================================================ error: proc macro panicked --> $DIR/invalid_juniper_directive_definition.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | string: String! 7 | | } ... | 19 | | ) on FIELD 20 | | } | |_^ | = help: message: error: Invalid location for @juniper directive: `FIELD` --> schema:1:59 | 1 | type Query { string : String ! } schema { query : Query } directive @ | ^ Location must be `FIELD_DEFINITION | SCALAR` error: Missing `FIELD_DEFINITION` directive location for @juniper directive --> schema:1:59 | 1 | type Query { string : String ! } schema { query : Query } directive @ | ^ Location must be `FIELD_DEFINITION | SCALAR` error: Missing `SCALAR` directive location for @juniper directive --> schema:1:59 | 1 | type Query { string : String ! } schema { query : Query } directive @ | ^ Location must be `FIELD_DEFINITION | SCALAR` error: Missing default value for `ownership` argument. Must be `"borrowed"` --> schema:2:9 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ error: `ownership` argument must have type `String` --> schema:2:9 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ Got `Boolean` error: Invalid default value for `infallible` argument. Must be `false` --> schema:2:30 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ Got `"foo"` error: `infallible` argument must have type `Boolean` --> schema:2:30 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ Got `String` error: Invalid default value for `with_time_zone` argument. Must be `true` --> schema:2:59 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ Got `false` error: `with_time_zone` argument must have type `Boolean` --> schema:2:59 | 2 | juniper(ownership : Boolean, infallible : String = "foo", with_time_zone : | ^ Got `[String]` error: Invalid default value for `async` argument. Must be `false` --> schema:3:27 | 3 | [String] = false, async : Boolean = true, stream_type : String = null, | ^ Got `true` error: Invalid default value for `stream_item_infallible` argument. Must be `true` --> schema:4:9 | 4 | stream_item_infallible : Boolean = false, bar : [Boolean]) on FIELD | ^ Got `false` error: Invalid argument for @juniper directive: `bar` --> schema:4:51 | 4 | stream_item_infallible : Boolean = false, bar : [Boolean]) on FIELD | ^ Supported arguments are `ownership`, `infallible`, `with_time_zone`, `async`, `stream_item_infallible`, and `stream_type` aborting due to 12 errors error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/invalid_juniper_directive_definition.rs:24:6 | 24 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_stream_return_type.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(infallible: true, ownership: "owned", stream_type: "123") } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } pub struct Subscription; impl SubscriptionFields for Subscription { fn field_users<'r, 'a>( &self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> Box + Send + Unpin> { Box::new(juniper_from_schema::futures::stream::iter(vec![])) } } pub struct User { id: ID, name: String, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { todo!() } fn field_name(&self, _: &Executor) -> FieldResult<&String> { todo!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/invalid_stream_return_type.stderr ================================================ error: proc macro panicked --> $DIR/invalid_stream_return_type.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | ping: Boolean! 7 | | } ... | 21 | | } 22 | | } | |_^ | = help: message: error: Invalid stream return type. This doesn't seem to be a valid Rust type --> schema:3:5 | 3 | users : User ! @ | ^ expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/invalid_stream_return_type.rs:26:6 | 26 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope error[E0405]: cannot find trait `SubscriptionFields` in this scope --> $DIR/invalid_stream_return_type.rs:34:6 | 34 | impl SubscriptionFields for Subscription { | ^^^^^^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `QueryTrail` in this scope --> $DIR/invalid_stream_return_type.rs:38:13 | 38 | _: &QueryTrail<'r, User, Walked>, | ^^^^^^^^^^ not found in this scope | help: consider importing this struct | 1 | use juniper_from_schema::QueryTrail; | error[E0412]: cannot find type `Walked` in this scope --> $DIR/invalid_stream_return_type.rs:38:34 | 38 | _: &QueryTrail<'r, User, Walked>, | ^^^^^^ not found in this scope | help: consider importing this struct | 1 | use juniper_from_schema::Walked; | error[E0405]: cannot find trait `UserFields` in this scope --> $DIR/invalid_stream_return_type.rs:49:6 | 49 | impl UserFields for User { | ^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/scalar_with_built_in_name.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { schema { query: Query } type Query { // The directive makes the return value `FieldResult` // rather than the default `FieldResult<&String>` helloWorld(name: String!): String! @juniper(ownership: "owned") } scalar String } pub struct Query; impl QueryFields for Query { fn field_hello_world(&self, executor: &Executor, name: String) -> FieldResult { todo!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/scalar_with_built_in_name.stderr ================================================ error: proc macro panicked --> $DIR/scalar_with_built_in_name.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | schema { 6 | | query: Query 7 | | } ... | 15 | | scalar String 16 | | } | |_^ | = help: message: error: You cannot declare scalars with names matching a built-in --> schema:3:1 | 3 | scalar String | ^ aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/scalar_with_built_in_name.rs:20:6 | 20 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_input_object_types.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: String! } schema { query: Query } input SomeInput { snake_cased: String! } } pub struct Query; impl QueryFields for Query { fn field_field(&self, _: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_input_object_types.stderr ================================================ error: proc macro panicked --> $DIR/snake_cased_fields_on_input_object_types.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | field: String! 7 | | } ... | 13 | | } 14 | | } | |_^ | = help: message: error: Field names must be camelCase, not snake_case --> schema:2:3 | 2 | { snake_cased : String ! } | ^ This is because Juniper always converts all field names to camelCase aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/snake_cased_fields_on_input_object_types.rs:18:6 | 18 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_interfaces.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: SomeInterface! } schema { query: Query } interface SomeInterface { snake_cased: String! } } pub struct Query; impl QueryFields for Query { fn field_field( &self, _: &Executor, _: &QueryTrail, ) -> FieldResult<&SomeInterface> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_interfaces.stderr ================================================ error: proc macro panicked --> $DIR/snake_cased_fields_on_interfaces.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | field: SomeInterface! 7 | | } ... | 13 | | } 14 | | } | |_^ | = help: message: error: Field names must be camelCase, not snake_case --> schema:2:17 | 2 | SomeInterface { snake_cased : String ! } | ^ This is because Juniper always converts all field names to camelCase aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/snake_cased_fields_on_interfaces.rs:18:6 | 18 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `QueryTrail` in this scope --> $DIR/snake_cased_fields_on_interfaces.rs:22:13 | 22 | _: &QueryTrail, | ^^^^^^^^^^ not found in this scope | help: consider importing this struct | 1 | use juniper_from_schema::QueryTrail; | error[E0412]: cannot find type `SomeInterface` in this scope --> $DIR/snake_cased_fields_on_interfaces.rs:22:24 | 18 | impl QueryFields for Query { | - help: you might be missing a type parameter: `` ... 22 | _: &QueryTrail, | ^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `Walked` in this scope --> $DIR/snake_cased_fields_on_interfaces.rs:22:39 | 22 | _: &QueryTrail, | ^^^^^^ not found in this scope | help: consider importing this struct | 1 | use juniper_from_schema::Walked; | error[E0412]: cannot find type `SomeInterface` in this scope --> $DIR/snake_cased_fields_on_interfaces.rs:23:23 | 18 | impl QueryFields for Query { | - help: you might be missing a type parameter: `` ... 23 | ) -> FieldResult<&SomeInterface> { | ^^^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_types.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { snake_cased: String! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_snake_cased(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/snake_cased_fields_on_types.stderr ================================================ error: proc macro panicked --> $DIR/snake_cased_fields_on_types.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | snake_cased: String! 7 | | } 8 | | 9 | | schema { query: Query } 10 | | } | |_^ | = help: message: error: Field names must be camelCase, not snake_case --> schema:1:14 | 1 | type Query { snake_cased : String ! } schema { query : Query } | ^ This is because Juniper always converts all field names to camelCase aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/snake_cased_fields_on_types.rs:14:6 | 14 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/unknown_directive.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! @someDirectiveThatIsntNotSupported } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/unknown_directive.stderr ================================================ error: proc macro panicked --> $DIR/unknown_directive.rs:4:1 | 4 | / juniper_from_schema::graphql_schema! { 5 | | type Query { 6 | | string: String! @someDirectiveThatIsntNotSupported 7 | | } 8 | | 9 | | schema { query: Query } 10 | | } | |_^ | = help: message: error: Unknown directive --> schema:1:32 | 1 | type Query { string : String ! @ someDirectiveThatIsntNotSupported } schema | ^ aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/unknown_directive.rs:14:6 | 14 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/unsupported_config.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema_from_file!( "../../../juniper-from-schema/tests/schemas/customizing_context_name.graphql", foo: Foo ); pub struct Query; impl QueryFields for Query { fn field_foo(&self, _: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_fail/unsupported_config.stderr ================================================ error: Unknown `graphql_schema_from_file` config `foo` Supported configs are `error_type` and `context_type` --> $DIR/unsupported_config.rs:6:5 | 6 | foo: Foo | ^^^ error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/unsupported_config.rs:11:6 | 11 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_fail/uppercase_uuid.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); use uuid::Uuid; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { uuid: UUID! @juniper(ownership: "owned") } scalar UUID } pub struct Query; impl QueryFields for Query { fn field_uuid(&self, _: &Executor) -> FieldResult { Ok(Uuid::new_v4()) } } ================================================ FILE: juniper-from-schema/tests/compile_fail/uppercase_uuid.stderr ================================================ error: proc macro panicked --> $DIR/uppercase_uuid.rs:6:1 | 6 | / juniper_from_schema::graphql_schema! { 7 | | schema { 8 | | query: Query 9 | | } ... | 15 | | scalar UUID 16 | | } | |_^ | = help: message: error: The UUID must be named `Uuid` --> schema:2:50 | 2 | { uuid : UUID ! @ juniper(ownership : "owned") } scalar UUID | ^ This is to be consistent with the naming the "uuid" crate aborting due to previous error error[E0405]: cannot find trait `QueryFields` in this scope --> $DIR/uppercase_uuid.rs:20:6 | 20 | impl QueryFields for Query { | ^^^^^^^^^^^ not found in this scope ================================================ FILE: juniper-from-schema/tests/compile_pass/async.rs ================================================ #![allow( dead_code, unused_mut, unused_variables, unused_must_use, unused_imports )] include!("setup.rs"); use std::future::Future; use std::task::Poll; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { asyncPing: Boolean! @juniper(infallible: true, ownership: "owned", async: true) syncPing: Boolean! @juniper(infallible: true, ownership: "owned", async: false) } type User implements Entity { id: ID! @juniper(infallible: true, ownership: "owned", async: true) } interface Entity { id: ID! @juniper(infallible: true, ownership: "owned", async: true) } } pub struct Query; #[juniper_from_schema::juniper::async_trait] impl QueryFields for Query { async fn field_async_ping<'s, 'r, 'a>(&'s self, _: &Executor<'r, 'a, Context>) -> bool { ready(true).await } fn field_sync_ping(&self, _: &Executor) -> bool { true } } pub struct User; #[juniper_from_schema::juniper::async_trait] impl UserFields for User { async fn field_id<'s, 'r, 'a>(&'s self, _: &Executor<'r, 'a, Context>) -> ID { todo!() } } // copied from std because it isn't stable yet pub struct Ready(Option); impl Unpin for Ready {} impl Future for Ready { type Output = T; fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll { todo!() } } pub fn ready(t: T) -> Ready { todo!() } ================================================ FILE: juniper-from-schema/tests/compile_pass/async_as_ref.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { schema { query: Query } type Query { find(id: ID!): User @juniper(ownership: "as_ref", async: true) } type User { id: ID! @juniper(infallible: true) } } pub struct Query; #[juniper_from_schema::juniper::async_trait] impl QueryFields for Query { async fn field_find<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, trail: &QueryTrail<'r, User, Walked>, id: ID, ) -> FieldResult> { todo!() } } #[derive(Debug)] pub struct User { id: ID, } impl UserFields for User { fn field_id(&self, _: &Executor) -> &ID { todo!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/async_field_returning_type.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); use juniper_from_schema::*; use url::Url; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { find(id: ID!): User! @juniper(ownership: "owned", async: true) } type User { id: ID! @juniper(infallible: true) } } pub struct Query; #[juniper_from_schema::juniper::async_trait] impl QueryFields for Query { async fn field_find<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, trail: &QueryTrail<'r, User, Walked>, id: ID, ) -> FieldResult { todo!() } } #[derive(Debug)] pub struct User { id: ID, } impl UserFields for User { fn field_id(&self, _: &Executor) -> &ID { todo!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/async_returning_reference.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); use juniper_from_schema::*; use url::Url; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { find(id: ID!): User! @juniper(async: true) } type User { id: ID! @juniper(infallible: true) } } pub struct Query; #[juniper_from_schema::juniper::async_trait] impl QueryFields for Query { async fn field_find<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, trail: &QueryTrail<'r, User, Walked>, id: ID, ) -> FieldResult<&'s User> { todo!() } } #[derive(Debug)] pub struct User { id: ID, } impl UserFields for User { fn field_id(&self, _: &Executor) -> &ID { todo!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/correct_executor_signature.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: Int! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field(&self, executor: &Executor) -> FieldResult<&i32> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/custom_scalar.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { scalar Cursor type Query { field(arg: Cursor!): Cursor! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field<'a>( &self, executor: &Executor, arg: Cursor, ) -> FieldResult<&Cursor> { Cursor::new("123"); unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/customizing_context_name.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema_from_file!( "../../../juniper-from-schema/tests/schemas/customizing_context_name.graphql", context_type: MyContext ); pub struct MyContext; impl juniper::Context for MyContext {} pub struct Query; impl QueryFields for Query { fn field_foo(&self, _: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/customizing_the_error_type.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema_from_file!( "../../../juniper-from-schema/tests/schemas/very_simple_schema.graphql", error_type: MyError, ); pub enum MyError { Foo, Bar, } impl juniper::IntoFieldError for MyError { fn into_field_error(self) -> juniper::FieldError { unimplemented!() } } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> Result<&String, MyError> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/dates_and_times.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); use chrono::{naive::NaiveDate, prelude::*}; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { date: Date! @juniper(ownership: "owned") dateTime: DateTimeUtc! @juniper(ownership: "owned") } scalar Date scalar DateTimeUtc } pub struct Query; impl QueryFields for Query { fn field_date(&self, _: &Executor) -> FieldResult { unimplemented!() } fn field_date_time(&self, _: &Executor) -> FieldResult> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/directive_definitions_are_allowed.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! } schema { query: Query } directive @foo(bar: [String!]!) on FIELD } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/empty_mutations.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } fn this_should_compile() { let _ = juniper::execute_sync( "query Foo { string }", None, &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), &Variables::new(), &Context, ) .unwrap(); } ================================================ FILE: juniper-from-schema/tests/compile_pass/enums.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { enum YesNo { YES NO NOT_SURE } type Query { yesNo(arg: YesNo): YesNo! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_yes_no( &self, executor: &Executor, arg: Option, ) -> FieldResult<&YesNo> { let _: YesNo = YesNo::No; let _: YesNo = YesNo::Yes; let _: YesNo = YesNo::NotSure; unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/field_args.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { single(arg: Int!): Int! multiple(one: Int!, two: String, three: [Float]): Int! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_single(&self, executor: &Executor, arg: i32) -> FieldResult<&i32> { unimplemented!() } fn field_multiple( &self, executor: &Executor, one: i32, two: Option, three: Option>>, ) -> FieldResult<&i32> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/infallible_directive.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { schema { query: Query } type Query { unowned: String! @juniper(infallible: true) owned: String! @juniper(ownership: "owned", infallible: true) ownedReordered: String! @juniper(infallible: true, ownership: "owned") } type User implements Entity { id: ID! @juniper(infallible: true) } interface Entity { id: ID! @juniper(infallible: true) } } pub struct Query; impl QueryFields for Query { fn field_unowned(&self, _: &Executor) -> &String { unimplemented!() } fn field_owned(&self, _: &Executor) -> String { unimplemented!() } fn field_owned_reordered(&self, _: &Executor) -> String { unimplemented!() } } pub struct User; impl UserFields for User { fn field_id(&self, _: &Executor) -> &ID { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/input_object.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { usersAtLocation(coordinate: Coordinate): Boolean! } input Coordinate { lat: Int! long: Int! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_users_at_location( &self, executor: &Executor, coordinate: Option, ) -> FieldResult<&bool> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/input_object_clone.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { usersAtLocation(coordinate: Coordinate): Boolean! } input Coordinate { lat: Int! long: Int! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_users_at_location( &self, executor: &Executor, coordinate: Option, ) -> FieldResult<&bool> { let coord = coordinate.clone(); unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/input_objects_have_public_fields.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); mod schema { use super::*; juniper_from_schema::graphql_schema! { type Query { usersAtLocation(coordinate: Coordinate!): Boolean! } input Coordinate { lat: Int! long: Int! } schema { query: Query } } } pub struct Query; impl schema::QueryFields for Query { fn field_users_at_location( &self, executor: &Executor, coordinate: schema::Coordinate, ) -> FieldResult<&bool> { coordinate.lat; coordinate.long; unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/naive_date_time.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); use chrono::prelude::*; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { dateTime: DateTimeUtc! @juniper(ownership: "owned") } scalar DateTimeUtc @juniper(with_time_zone: false) } pub struct Query; impl QueryFields for Query { fn field_date_time(&self, _: &Executor) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/non_null_list_non_null_items.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: [Int!]! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field(&self, executor: &Executor) -> FieldResult<&Vec> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/non_null_list_nullable_items.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: [Int]! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field(&self, executor: &Executor) -> FieldResult<&Vec>> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/nullable_list_non_null_items.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: [Int!] } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field(&self, executor: &Executor) -> FieldResult<&Option>> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/nullable_list_nullable_items.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { field: [Int] } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_field( &self, executor: &Executor, ) -> FieldResult<&Option>>> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/ownership_attributes.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { borrowedString: String! @juniper(ownership: "borrowed") ownedString: String! @juniper(ownership: "owned") asRefString: String @juniper(ownership: "as_ref") } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_borrowed_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } fn field_owned_string(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_as_ref_string<'s>( &'s self, executor: &Executor, ) -> FieldResult> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/query_trail.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { user: User! } type User { id: Int! club: Club club2: Club! } type Club { id: Int! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_user( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult<&User> { trail.club().walk(); trail.club2().walk(); trail.club2().id() == true; unimplemented!() } } pub struct User { id: i32, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { unimplemented!() } fn field_club( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult<&Option> { unimplemented!() } fn field_club2( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult<&Club> { unimplemented!() } } pub struct Club { id: i32, } impl ClubFields for Club { fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/query_trail_methods_for_interfaces.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { posts: [Post!]! @juniper(ownership: "owned") } type Post { comments: [Comment!]! @juniper(ownership: "owned") } interface Entity { id: Int! @juniper(ownership: "owned") country: Country! @juniper(ownership: "owned") } type User implements Entity { country: Country! @juniper(ownership: "owned") id: Int! @juniper(ownership: "owned") } type Country { id: Int! @juniper(ownership: "owned") } type Comment { author: Entity! @juniper(ownership: "owned") id: Int! @juniper(ownership: "owned") } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_posts( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { unimplemented!() } } pub struct Post { comments: Vec, } impl PostFields for Post { fn field_comments( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { unimplemented!() } } pub struct Comment { id: i32, } impl CommentFields for Comment { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_author( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { if trail.id() { // } if trail.country().id() { // } unimplemented!() } } pub struct User { id: i32, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_country( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { unimplemented!() } } pub struct Country { id: i32, } impl CountryFields for Country { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/query_trail_methods_for_union_types.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { posts: [Post!]! @juniper(ownership: "owned") } type Post { comments: [Comment!]! @juniper(ownership: "owned") } union Entity = User | Company type User { country: Country! @juniper(ownership: "owned") id: Int! @juniper(ownership: "owned") } type Company { countryOfOperation: Country! @juniper(ownership: "owned") id: Int! @juniper(ownership: "owned") name: String! @juniper(ownership: "owned") } type Country { id: Int! @juniper(ownership: "owned") } type Comment { author: Entity! @juniper(ownership: "owned") id: Int! @juniper(ownership: "owned") } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_posts( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { unimplemented!() } } pub struct Post { comments: Vec, } impl PostFields for Post { fn field_comments( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { unimplemented!() } } pub struct Comment { id: i32, } impl CommentFields for Comment { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_author( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { let _: bool = trail.id(); let _: bool = trail.country().id(); let _: QueryTrail = trail.country(); let _: bool = trail.country_of_operation().id(); let _: QueryTrail = trail.country_of_operation(); let _: bool = trail.name(); unimplemented!() } } pub struct User { id: i32, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_country( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { unimplemented!() } } pub struct Company { id: i32, } impl CompanyFields for Company { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_name(&self, executor: &Executor) -> FieldResult { unimplemented!() } fn field_country_of_operation( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { unimplemented!() } } pub struct Country { id: i32, } impl CountryFields for Country { fn field_id(&self, executor: &Executor) -> FieldResult { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/returning_references.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema_from_file!( "../../../juniper-from-schema/tests/schemas/returning_references.graphql" ); pub struct Query; impl QueryFields for Query { fn field_user_nullable( &self, executor: &Executor, trail: &QueryTrail, id: i32, ) -> FieldResult> { Ok(find_user(id)) } fn field_user_non_null( &self, executor: &Executor, trail: &QueryTrail, id: i32, ) -> FieldResult { Ok(find_user(id).unwrap()) } } pub struct User { id: i32, name: String, name_nullable: Option, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult<&i32> { Ok(&self.id) } fn field_name_nullable( &self, executor: &Executor, ) -> FieldResult> { Ok(self.name_nullable.clone()) } fn field_name_non_null(&self, executor: &Executor) -> FieldResult<&String> { Ok(&self.name) } } fn find_user(id: i32) -> Option { Some(User { id, name: "Bob".to_string(), name_nullable: None, }) } ================================================ FILE: juniper-from-schema/tests/compile_pass/setup.rs ================================================ use juniper::{EmptyMutation, EmptySubscription, Executor, FieldResult, Variables, ID}; use std::pin::Pin; pub struct Context; impl juniper::Context for Context {} fn main() {} #[allow(dead_code)] fn __use_all_the_imports( _: EmptyMutation<()>, _: Executor<()>, _: FieldResult<(), ()>, _: Variables, _: ID, _: EmptySubscription<()>, _: Pin<()>, ) { } ================================================ FILE: juniper-from-schema/tests/compile_pass/simple_non_null_scalars.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! float: Float! int: Int! boolean: Boolean! } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } fn field_float(&self, executor: &Executor) -> FieldResult<&f64> { unimplemented!() } fn field_int(&self, executor: &Executor) -> FieldResult<&i32> { unimplemented!() } fn field_boolean(&self, executor: &Executor) -> FieldResult<&bool> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/simple_nullable_scalars.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String float: Float int: Int boolean: Boolean } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&Option> { unimplemented!() } fn field_float(&self, executor: &Executor) -> FieldResult<&Option> { unimplemented!() } fn field_int(&self, executor: &Executor) -> FieldResult<&Option> { unimplemented!() } fn field_boolean(&self, executor: &Executor) -> FieldResult<&Option> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/compile_pass/url.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); use url::Url; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { url: Url! @juniper(ownership: "owned") } scalar Url } pub struct Query; impl QueryFields for Query { fn field_url(&self, _: &Executor) -> FieldResult { let url = Url::parse("https://example.com").unwrap(); Ok(url) } } ================================================ FILE: juniper-from-schema/tests/compile_pass/uuid.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("setup.rs"); use uuid::Uuid; juniper_from_schema::graphql_schema! { schema { query: Query } type Query { uuid: Uuid! @juniper(ownership: "owned") } scalar Uuid } pub struct Query; impl QueryFields for Query { fn field_uuid(&self, _: &Executor) -> FieldResult { Ok(Uuid::new_v4()) } } ================================================ FILE: juniper-from-schema/tests/compile_pass/valid_juniper_directive_definition.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { string: String! } schema { query: Query } directive @juniper( ownership: String = "borrowed", infallible: Boolean = false, with_time_zone: Boolean = true, async: Boolean = false, stream_item_infallible: Boolean = true, stream_type: String = null ) on FIELD_DEFINITION | SCALAR } pub struct Query; impl QueryFields for Query { fn field_string(&self, executor: &Executor) -> FieldResult<&String> { unimplemented!() } } ================================================ FILE: juniper-from-schema/tests/converting_query_trails_test.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] use juniper::{EmptyMutation, Executor, FieldResult, Variables}; use juniper_from_schema::{graphql_schema, graphql_schema_from_file}; pub struct Context; impl juniper::Context for Context {} graphql_schema! { type Query { entities: [Entity!]! @juniper(ownership: "owned") search(query: String!): [SearchResult!]! @juniper(ownership: "owned") } interface Entity { id: Int! @juniper(ownership: "owned") name: String! } type User implements Entity { id: Int! @juniper(ownership: "owned") name: String! } union SearchResult = User schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_entities( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult> { verify_entity_query_trail(trail); verify_user_query_trail(&trail.downcast()); Ok(vec![]) } fn field_search( &self, executor: &Executor, trail: &QueryTrail, _query: String, ) -> FieldResult> { verify_search_result_query_trail(trail); verify_user_query_trail(&trail.downcast()); Ok(vec![]) } } fn verify_entity_query_trail(trail: &QueryTrail) { if !trail.id() { panic!("Entity.id missing from trail") } } fn verify_search_result_query_trail(trail: &QueryTrail) { if !trail.id() { panic!("id missing from trail") } } fn verify_user_query_trail(trail: &QueryTrail) { if !trail.id() { panic!("User.id missing from trail") } } pub struct User { id: i32, name: String, } impl UserFields for User { fn field_id(&self, executor: &Executor) -> FieldResult { Ok(self.id) } fn field_name(&self, executor: &Executor) -> FieldResult<&String> { Ok(&self.name) } } #[test] fn test_converting_interface_trails() { query( r#" query { entities { id } } "#, ); } #[test] #[should_panic] fn test_converting_interface_trails_negative() { query( r#" query { entities { name } } "#, ); } #[test] fn test_converting_union_trails() { query( r#" query { search(query: "foo") { ... on User { id } } } "#, ); } #[test] #[should_panic] fn test_converting_union_trails_negative() { query( r#" query { search(query: "foo") { ... on User { name } } } "#, ); } fn query(query: &str) { let ctx = Context; let (juniper_value, _errors) = juniper::execute_sync( query, None, &Schema::new( Query, juniper::EmptyMutation::new(), juniper::EmptySubscription::new(), ), &Variables::new(), &ctx, ) .unwrap(); } ================================================ FILE: juniper-from-schema/tests/default_argument_values_test.rs ================================================ #![allow(clippy::let_unit_value)] #![allow(dead_code, unused_variables, unused_imports)] #[macro_use] extern crate juniper; use assert_json_diff::assert_json_include; use juniper::{Executor, FieldResult, Variables}; use juniper_from_schema::{graphql_schema, graphql_schema_from_file}; use serde_json::{self, json, Value}; use std::collections::HashMap; graphql_schema! { type Query { int(arg: Int = 1): Int! @juniper(ownership: "owned") float(arg: Float = 1.5): Float! @juniper(ownership: "owned") string(arg: String = "foo"): String! @juniper(ownership: "owned") boolean(arg: Boolean = true): Boolean! @juniper(ownership: "owned") list(arg: [Int!] = [1, 2, 3]): [Int!]! @juniper(ownership: "owned") enumeration(arg: Unit = METER): Unit! @juniper(ownership: "owned") object(arg: CoordinateIn = { lat: 1.0, long: 2.0 }): CoordinateOut! @juniper(ownership: "owned") objectNullable(arg: Pagination = { pageSize: null }): Int @juniper(ownership: "owned") objectNullableSet(arg: Pagination = { pageSize: 1 }): Int @juniper(ownership: "owned") objectNullablePartial(arg: A = { a: "a arg" }): [String]! @juniper(ownership: "owned") objectNullableNesting(arg: B = { c: { x: 1 } }): [Int]! @juniper(ownership: "owned") } input CoordinateIn { lat: Float! long: Float! } type CoordinateOut { lat: Float! long: Float! } input Pagination { pageSize: Int } input A { a: String b: String } input B { c: C } input C { x: Int } enum Unit { METER FOOT } schema { query: Query } } pub struct Query; impl QueryFields for Query { fn field_int(&self, _: &Executor, arg: i32) -> FieldResult { Ok(arg) } fn field_float(&self, _: &Executor, arg: f64) -> FieldResult { Ok(arg) } fn field_string(&self, _: &Executor, arg: String) -> FieldResult { Ok(arg) } fn field_boolean(&self, _: &Executor, arg: bool) -> FieldResult { Ok(arg) } fn field_list(&self, _: &Executor, arg: Vec) -> FieldResult> { Ok(arg) } fn field_enumeration(&self, _: &Executor, arg: Unit) -> FieldResult { Ok(arg) } fn field_object( &self, _: &Executor, _: &QueryTrail, arg: CoordinateIn, ) -> FieldResult { Ok(CoordinateOut { lat: arg.lat, long: arg.long, }) } fn field_object_nullable( &self, _: &Executor, arg: Pagination, ) -> FieldResult> { Ok(arg.page_size) } fn field_object_nullable_set( &self, _: &Executor, arg: Pagination, ) -> FieldResult> { Ok(arg.page_size) } fn field_object_nullable_partial( &self, _: &Executor, arg: A, ) -> FieldResult>> { Ok(vec![arg.a, arg.b]) } fn field_object_nullable_nesting( &self, _: &Executor, b: B, ) -> FieldResult>> { Ok(vec![b.c.and_then(|c| c.x)]) } } pub struct CoordinateOut { pub lat: f64, pub long: f64, } impl CoordinateOutFields for CoordinateOut { fn field_lat(&self, _: &Executor) -> FieldResult<&f64> { Ok(&self.lat) } fn field_long(&self, _: &Executor) -> FieldResult<&f64> { Ok(&self.long) } } type Context = (); #[test] fn test_int() { let value = run_query(r#"query { int }"#); assert_json_include!(actual: value, expected: json!({ "int": 1 })); let value = run_query(r#"query { int(arg: 1337) }"#); assert_json_include!(actual: value, expected: json!({ "int": 1337 })); } #[test] fn test_float() { let value = run_query(r#"query { float }"#); assert_json_include!(actual: value, expected: json!({ "float": 1.5 })); let value = run_query(r#"query { float(arg: 1337.5) }"#); assert_json_include!(actual: value, expected: json!({ "float": 1337.5 })); } #[test] fn test_string() { let value = run_query(r#"query { string }"#); assert_json_include!(actual: value, expected: json!({ "string": "foo" })); let value = run_query(r#"query { string(arg: "bar") }"#); assert_json_include!(actual: value, expected: json!({ "string": "bar" })); } #[test] fn test_boolean() { let value = run_query(r#"query { boolean }"#); assert_json_include!(actual: value, expected: json!({ "boolean": true })); let value = run_query(r#"query { boolean(arg: false) }"#); assert_json_include!(actual: value, expected: json!({ "boolean": false })); } #[test] fn test_list() { let value = run_query(r#"query { list }"#); assert_json_include!(actual: value, expected: json!({ "list": [1, 2, 3] })); let value = run_query(r#"query { list(arg: [1337]) }"#); assert_json_include!(actual: value, expected: json!({ "list": [1337] })); } #[test] fn test_enumeration() { let value = run_query(r#"query { enumeration }"#); assert_json_include!(actual: value, expected: json!({ "enumeration": "METER" })); let value = run_query(r#"query { enumeration(arg: FOOT) }"#); assert_json_include!(actual: value, expected: json!({ "enumeration": "FOOT" })); } #[test] fn test_object() { let value = run_query(r#"query { object { lat long } }"#); assert_json_include!( actual: value, expected: json!({ "object": { "lat": 1.0, "long": 2.0 } }) ); let value = run_query(r#"query { object(arg: { lat: 10.0, long: 20.0 }) { lat long } }"#); assert_json_include!( actual: value, expected: json!({ "object": { "lat": 10.0, "long": 20.0 } }) ); } #[test] fn test_object_nullable() { let value = run_query(r#"query { objectNullable }"#); assert_json_include!(actual: value, expected: json!({ "objectNullable": null })); let value = run_query(r#"query { objectNullable(arg: { pageSize: 1 }) }"#); assert_json_include!(actual: value, expected: json!({ "objectNullable": 1 })); } #[test] fn test_object_nullable_set() { let value = run_query(r#"query { objectNullableSet }"#); assert_json_include!(actual: value, expected: json!({ "objectNullableSet": 1 })); let value = run_query(r#"query { objectNullableSet(arg: { pageSize: 2 }) }"#); assert_json_include!(actual: value, expected: json!({ "objectNullableSet": 2 })); let value = run_query(r#"query { objectNullableSet(arg: { pageSize: null }) }"#); assert_json_include!( actual: value, expected: json!({ "objectNullableSet": null }) ); } #[test] fn test_object_partial() { let value = run_query(r#"query { objectNullablePartial }"#); assert_json_include!( actual: value, expected: json!({ "objectNullablePartial": ["a arg", null] }) ); let value = run_query(r#"query { objectNullablePartial(arg: { a: "a field" }) }"#); assert_json_include!( actual: value, expected: json!({ "objectNullablePartial": ["a field", null] }) ); let value = run_query(r#"query { objectNullablePartial(arg: { b: "b field" }) }"#); assert_json_include!( actual: value, expected: json!({ "objectNullablePartial": [null, "b field"] }) ); let value = run_query(r#"query { objectNullablePartial(arg: { a: "a field", b: "b field" }) }"#); assert_json_include!( actual: value, expected: json!({ "objectNullablePartial": ["a field", "b field"] }) ); } #[test] fn test_object_nesting() { let value = run_query(r#"query { objectNullableNesting }"#); assert_json_include!( actual: value, expected: json!({ "objectNullableNesting": [1] }) ); } fn run_query(query: &str) -> Value { let ctx = (); let (res, _errors) = juniper::execute_sync( query, None, &Schema::new( Query, juniper::EmptyMutation::new(), juniper::EmptySubscription::new(), ), &Variables::new(), &ctx, ) .unwrap(); let json = serde_json::from_str(&serde_json::to_string(&res).unwrap()).unwrap(); println!("--- -----------------"); println!("{}", serde_json::to_string_pretty(&json).unwrap()); println!("--- -----------------"); json } ================================================ FILE: juniper-from-schema/tests/doc_test.rs ================================================ #![recursion_limit = "128"] #![allow(dead_code)] #![allow(unused_braces)] #![deny(deprecated)] use assert_json_diff::assert_json_include; use juniper::{Executor, FieldResult, Variables, ID}; use juniper_from_schema::graphql_schema_from_file; use serde_json::{self, json, Value}; // The query that GraphiQL runs to inspect the schema static SCHEMA_INTROSPECTION_QUERY: &str = r#" query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } "#; graphql_schema_from_file!("tests/schemas/doc_test.graphql"); pub struct Query; impl QueryFields for Query { fn field_query_field(&self, _: &Executor, _: InputType) -> FieldResult<&SomeScalar> { unimplemented!() } fn field_entity( &self, _: &Executor, _: &QueryTrail, ) -> FieldResult<&Entity> { unimplemented!() } fn field_deprecated_field(&self, _: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_deprecated_field2(&self, _: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_search( &self, _: &Executor, _: &QueryTrail, _: String, ) -> FieldResult<&Vec> { unimplemented!() } } pub struct User { id: ID, user_type: UserType, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { unimplemented!() } fn field_user_type(&self, _: &Executor) -> FieldResult<&UserType> { unimplemented!() } fn field_interface_field(&self, _: &Executor, _: ID) -> FieldResult<&ID> { unimplemented!() } } use futures::stream::Stream; use std::pin::Pin; pub struct Subscription; impl SubscriptionFields for Subscription { fn field_subscription_field( &self, _: &Executor, _: InputType, ) -> Pin + Send>> { Box::pin(futures::stream::iter(vec![])) } } pub struct Context; impl juniper::Context for Context {} #[test] fn test_docs() { let mut json = introspect_schema()["__schema"]["types"] .as_array() .unwrap() .clone() .into_iter() .filter(|type_| !type_["name"].as_str().unwrap().starts_with("__")) .collect::>(); json.sort_by_key(|key| key["name"].as_str().unwrap().to_string()); let json = serde_json::Value::Array(json); println!("{}", serde_json::to_string_pretty(&json).unwrap()); assert_json_include!( actual: json, expected: json!([ { "name": "Boolean" }, { "name": "Entity", "description": "Entity desc", "fields": [ { "name": "id", "description": "Entity id desc", "isDeprecated": true, "deprecationReason": null, }, { "name": "interfaceField", "description": null, "isDeprecated": false, "deprecationReason": null, "args": [ { "name": "arg", // docs on interface field arguments are not supported in // juniper "description": null, } ] }, ], }, { "name": "ID" }, { "name": "InputType", "description": "InputType desc", "inputFields": [ { "name": "id", "description": "id desc", }, ] }, { "name": "Query", "description": "Root query type", "fields": [ { "name": "queryField", "description": "queryField desc", "isDeprecated": false, "args": [ { "name": "queryFieldArg", "description": "queryFieldArg desc", }, ], }, { "name": "deprecatedField", "description": "deprecatedField desc", "isDeprecated": true, "deprecationReason": null, }, { "name": "deprecatedField2", "description": "deprecatedField2 desc", "isDeprecated": true, "deprecationReason": "because reasons", }, ], }, { "name": "SearchResult", "description": "SearchResult desc", }, { "name": "SomeScalar", "description": "SomeScalar scalar desc", }, { "name": "String" }, { "name": "Subscription", "description": "Root subscription type", "fields": [ { "name": "subscriptionField", "description": "subscriptionField desc", "isDeprecated": false, "args": [ { "name": "subscriptionFieldArg", "description": "subscriptionFieldArg desc", }, ], }, ], }, { "name": "User" }, { "name": "UserType", "description": "UserType desc", "enumValues": [ { "name": "REAL", "description": "REAL desc", "deprecationReason": "because reasons", "isDeprecated": true, }, { "name": "BOT", "description": "BOT desc", "deprecationReason": null, "isDeprecated": false, }, { "name": "OTHER", "description": "OTHER desc", "deprecationReason": null, "isDeprecated": true, }, ], }, ]) ); } fn introspect_schema() -> Value { let ctx = Context; let (juniper_value, _errors) = juniper::execute_sync( SCHEMA_INTROSPECTION_QUERY, None, &Schema::new(Query, juniper::EmptyMutation::new(), Subscription), &Variables::new(), &ctx, ) .unwrap(); let json: Value = serde_json::from_str(&serde_json::to_string(&juniper_value).unwrap()).unwrap(); println!("{}", serde_json::to_string_pretty(&json).unwrap()); json } ================================================ FILE: juniper-from-schema/tests/end_to_end_test.rs ================================================ #![allow(dead_code, unused_variables, unused_imports)] #![allow(unused_braces)] use assert_json_diff::assert_json_include; use juniper::{Executor, FieldResult, Variables, ID}; use juniper_from_schema::{graphql_schema, graphql_schema_from_file}; use serde_json::{self, json, Value}; use std::collections::HashMap; graphql_schema_from_file!("tests/schemas/complex_schema.graphql"); pub struct Query; impl QueryFields for Query { fn field_hero( &self, executor: &Executor, trail: &QueryTrail, episode: Option, ) -> FieldResult> { let hero = episode.and_then(|episode| { let luke = executor .context() .db .humans .get(&"1") .map(|h| Character::from(h.clone())); match episode { Episode::Newhope => luke, Episode::Empire => luke, Episode::Jedi => luke, } }); Ok(hero) } fn field_search( &self, executor: &Executor, trail: &QueryTrail, text: Option, ) -> FieldResult>> { let results = text.map(|text| { executor .context() .db .humans .clone() .into_iter() .map(|(_, human)| human) .filter(|human| human.name.contains(&text)) .map(SearchResult::from) .collect::>() }); Ok(results) } } pub struct Mutation; impl MutationFields for Mutation { fn field_create_review( &self, executor: &Executor, trail: &QueryTrail, episode: Option, review: ReviewInput, ) -> FieldResult> { let review = Review { episode, stars: review.stars, commentary: review.commentary, favorite_color: review.favorite_color, }; // the fact that everything type checks is test enough, we don't need to actually insert // this review Ok(Some(review)) } } pub struct Review { episode: Option, stars: i32, commentary: Option, favorite_color: Option, } impl ReviewFields for Review { fn field_episode(&self, executor: &Executor) -> FieldResult<&Option> { Ok(&self.episode) } fn field_stars(&self, executor: &Executor) -> FieldResult<&i32> { Ok(&self.stars) } fn field_commentary(&self, executor: &Executor) -> FieldResult<&Option> { Ok(&self.commentary) } } #[derive(Clone)] pub struct Human { id: &'static str, name: String, } impl HumanFields for Human { fn field_id(&self, executor: &Executor) -> FieldResult { Ok(ID::new(self.id)) } fn field_name(&self, executor: &Executor) -> FieldResult<&String> { Ok(&self.name) } } #[derive(Clone)] pub struct Droid { id: &'static str, name: String, } impl DroidFields for Droid { fn field_id(&self, executor: &Executor) -> FieldResult { Ok(ID::new(self.id)) } fn field_name(&self, executor: &Executor) -> FieldResult<&String> { Ok(&self.name) } } pub struct Context { db: Db, } impl juniper::Context for Context {} pub struct Db { humans: HashMap<&'static str, Human>, } #[test] fn query_hero() { let value = run_query(r#"query { hero(episode: NEWHOPE) { id name } }"#); assert_json_include!( actual: value, expected: json!({ "hero": { "id": "1", "name": "Luke Skywalker", } }) ); let value = run_query(r#"query { hero(episode: EMPIRE) { id name } }"#); assert_json_include!( actual: value, expected: json!({ "hero": { "id": "1", "name": "Luke Skywalker", } }) ); let value = run_query(r#"query { hero(episode: JEDI) { id name } }"#); assert_json_include!( actual: value, expected: json!({ "hero": { "id": "1", "name": "Luke Skywalker", } }) ); } #[test] fn search() { let value = run_query( r#" query { search(text: "Luke") { ... on Human { id name } ... on Droid { id name } } } "#, ); assert_json_include!( actual: value, expected: json!({ "search": [ { "id": "1", "name": "Luke Skywalker" }, ] }) ); } fn run_query(query: &str) -> Value { let db = Db { humans: maplit::hashmap! { "1" => Human { id: "1", name: "Luke Skywalker".to_string() }, }, }; let ctx = Context { db }; let (res, _errors) = juniper::execute_sync( query, None, &Schema::new(Query, Mutation, juniper::EmptySubscription::new()), &Variables::new(), &ctx, ) .unwrap(); serde_json::from_str(&serde_json::to_string(&res).unwrap()).unwrap() } ================================================ FILE: juniper-from-schema/tests/launchpad.rs ================================================ // this file can be used for testing/debugging things before moving them into a trybuild test #![allow(warnings)] use futures::stream::Stream; use juniper::{EmptyMutation, Executor, FieldResult, Variables, ID}; use std::pin::Pin; pub struct Context; impl juniper::Context for Context {} fn main() {} ================================================ FILE: juniper-from-schema/tests/query_trail_arguments.rs ================================================ #![allow(clippy::too_many_arguments)] #![allow(dead_code, unused_variables, unused_imports)] #[macro_use] extern crate juniper; use assert_json_diff::assert_json_include; use chrono::prelude::*; use juniper::{Executor, FieldResult, Variables, ID}; use juniper_from_schema::{graphql_schema, graphql_schema_from_file}; use serde_json::{self, json, Value}; use std::collections::HashMap; use url::Url; use uuid::Uuid; graphql_schema! { schema { query: Query } type Query { a: A! @juniper(ownership: "owned") } type A { b: B! @juniper(ownership: "owned") } type B { c: C! @juniper(ownership: "owned") } type C { fieldWithArg( stringArg: String! nullableArg: String nullableArg2: String intArg: Int! floatArg: Float! boolArg: Boolean! listArg: [Int!]! enumArg: Color! objectArg: InputObject! cursorArg: Cursor! idArg: ID! urlArg: Url! uuidArg: Uuid! dateArg: Date! dateTimeArg: DateTimeUtc! defaultArg: String = "value set in schema" defaultArg2: String = "error" ): String! @juniper(ownership: "owned") fieldWithArgReturningType( stringArg: String! ): D! @juniper(ownership: "owned") } type D { value: String! @juniper(ownership: "owned") } input InputObject { value: String! } enum Color { RED BLUE } scalar Cursor scalar Url scalar Uuid scalar Date scalar DateTimeUtc } pub struct Query; impl QueryFields for Query { fn field_a( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { if let Some(c) = trail.b().c().walk() { assert_eq!("foo".to_string(), c.field_with_arg_args().string_arg()); assert_eq!(None, c.field_with_arg_args().nullable_arg()); assert_eq!( Some("bar".to_string()), c.field_with_arg_args().nullable_arg2() ); assert_eq!(1, c.field_with_arg_args().int_arg()); assert_eq!("2.5", c.field_with_arg_args().float_arg().to_string()); assert_eq!(false, c.field_with_arg_args().bool_arg()); assert_eq!(vec![1, 2, 3], c.field_with_arg_args().list_arg()); assert_eq!(Color::Red, c.field_with_arg_args().enum_arg()); assert_eq!( "baz".to_string(), c.field_with_arg_args().object_arg().value ); assert_eq!( Cursor("cursor-value".to_string()), c.field_with_arg_args().cursor_arg() ); assert_eq!(ID::new("id-value"), c.field_with_arg_args().id_arg()); assert_eq!( Url::parse("https://example.net").unwrap(), c.field_with_arg_args().url_arg() ); assert_eq!( Uuid::parse_str("46ebd0ee-0e6d-43c9-b90d-ccc35a913f3e").unwrap(), c.field_with_arg_args().uuid_arg() ); assert_eq!( NaiveDate::parse_from_str("2019-01-01", "%Y-%m-%d").unwrap(), c.field_with_arg_args().date_arg() ); assert_eq!( DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap(), c.field_with_arg_args().date_time_arg() ); assert_eq!( "value set in schema".to_string(), c.field_with_arg_args().default_arg() ); assert_eq!( "value set in query".to_string(), c.field_with_arg_args().default_arg2() ); assert_eq!( "qux".to_string(), c.field_with_arg_returning_type_args().string_arg() ); } Ok(A) } } pub struct A; impl AFields for A { fn field_b( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { Ok(B) } } pub struct B; impl BFields for B { fn field_c( &self, executor: &Executor, trail: &QueryTrail, ) -> FieldResult { Ok(C) } } pub struct C; impl CFields for C { fn field_field_with_arg( &self, executor: &Executor, _: String, _: Option, _: Option, _: i32, _: f64, _: bool, _: Vec, _: Color, _: InputObject, _: Cursor, _: ID, _: Url, _: Uuid, _: NaiveDate, _: DateTime, _: String, _: String, ) -> FieldResult { Ok(String::new()) } fn field_field_with_arg_returning_type( &self, executor: &Executor, _: &QueryTrail, _: String, ) -> FieldResult { Ok(D) } } pub struct D; impl DFields for D { fn field_value(&self, executor: &Executor) -> FieldResult { Ok(String::new()) } } #[test] fn scalar_values() { let value = run_query( r#"query { a { b { c { fieldWithArg( stringArg: "foo", nullableArg: null, nullableArg2: "bar", intArg: 1, floatArg: 2.5, boolArg: false, listArg: [1, 2, 3], enumArg: RED, objectArg: { value: "baz" }, cursorArg: "cursor-value", idArg: "id-value", urlArg: "https://example.net", uuidArg: "46ebd0ee-0e6d-43c9-b90d-ccc35a913f3e", dateArg: "2019-01-01", dateTimeArg: "1996-12-19T16:39:57-08:00", defaultArg2: "value set in query", ) fieldWithArgReturningType( stringArg: "qux", ) { value } } } } }"#, ); assert_json_include!( actual: value, expected: json!({ "a": { "b": { "c": {} } } }) ); } type Context = (); fn run_query(query: &str) -> Value { let (res, _errors) = juniper::execute_sync( query, None, &Schema::new( Query, juniper::EmptyMutation::new(), juniper::EmptySubscription::new(), ), &Variables::new(), &(), ) .unwrap(); let json = serde_json::from_str(&serde_json::to_string(&res).unwrap()).unwrap(); println!("--- -----------------"); println!("{}", serde_json::to_string_pretty(&json).unwrap()); println!("--- -----------------"); json } ================================================ FILE: juniper-from-schema/tests/schemas/complex_schema.graphql ================================================ # From https://github.com/apollographql/starwars-server/blob/master/data/swapiSchema.js # Missing: # - subscription type schema { query: Query mutation: Mutation } "The query type, represents all of the entry points into our object graph" type Query { hero(episode: Episode): Character @juniper(ownership: "owned") search(text: String): [SearchResult!] @juniper(ownership: "owned") } "The mutation type, represents all updates we can make to our data" type Mutation { createReview(episode: Episode, review: ReviewInput!): Review @juniper(ownership: "owned") } "The episodes in the Star Wars trilogy" enum Episode { "Star Wars Episode IV: A New Hope, released in 1977." NEWHOPE "Star Wars Episode V: The Empire Strikes Back, released in 1980." EMPIRE "Star Wars Episode VI: Return of the Jedi, released in 1983." JEDI } "A character from the Star Wars universe" interface Character { """ The ID of the character """ id: ID! @juniper(ownership: "owned") "The name of the character" name: String! } "A humanoid creature from the Star Wars universe" type Human implements Character { """ The ID of the human """ id: ID! @juniper(ownership: "owned") "What this human calls themselves" name: String! } "A humanoid creature from the Star Wars universe" type Droid implements Character { """ The ID of the human """ id: ID! @juniper(ownership: "owned") "What this human calls themselves" name: String! } union SearchResult = Human | Droid input ReviewInput { "0-5 stars" stars: Int! "Comment about the movie, optional" commentary: String "Favorite color, optional" favoriteColor: ColorInput } "The input object sent when passing in a color" input ColorInput { red: Int! green: Int! blue: Int! } "Represents a review for a movie" type Review { "The movie" episode: Episode "The number of stars this review gave, 1-5" stars: Int! "Comment about the movie" commentary: String } ================================================ FILE: juniper-from-schema/tests/schemas/customizing_context_name.graphql ================================================ type Query { foo: String! } schema { query: Query } ================================================ FILE: juniper-from-schema/tests/schemas/doc_schema.graphql ================================================ schema { query: Query mutation: Mutation } type Query { helloWorld(name: String!): String! @juniper(ownership: "owned") } type Mutation { noop: Boolean! } ================================================ FILE: juniper-from-schema/tests/schemas/doc_test.graphql ================================================ schema { query: Query subscription: Subscription } "Root query type" type Query { "queryField desc" queryField( "queryFieldArg desc" queryFieldArg: InputType! ): SomeScalar! "deprecatedField desc" deprecatedField: ID! @deprecated "deprecatedField2 desc" deprecatedField2: ID! @deprecated(reason: "because reasons") entity: Entity! search(query: String!): [SearchResult!]! } "Root subscription type" type Subscription { "subscriptionField desc" subscriptionField( "subscriptionFieldArg desc" subscriptionFieldArg: InputType! ): SomeScalar! @juniper(ownership: "owned", infallible: true) } "SomeScalar scalar desc" scalar SomeScalar "InputType desc" input InputType { "id desc" id: ID! } type User implements Entity { id: ID! userType: UserType! interfaceField( "interface field arg desc" arg: ID! ): ID! } "Entity desc" interface Entity { "Entity id desc" id: ID! @deprecated interfaceField( "interface field arg desc" arg: ID! ): ID! } "UserType desc" enum UserType { "REAL desc" REAL @deprecated(reason: "because reasons") "BOT desc" BOT "OTHER desc" OTHER @deprecated } "SearchResult desc" union SearchResult = User ================================================ FILE: juniper-from-schema/tests/schemas/original_complex.graphql ================================================ # From https://github.com/apollographql/starwars-server/blob/master/data/swapiSchema.js # Missing: # - subscription type schema { query: Query mutation: Mutation } "The query type, represents all of the entry points into our object graph" type Query { hero(episode: Episode): Character reviews(episode: Episode!): [Review] search(text: String): [SearchResult] character(id: ID!): Character droid(id: ID!): Droid human(id: ID!): Human starship(id: ID!): Starship } "The mutation type, represents all updates we can make to our data" type Mutation { createReview(episode: Episode, review: ReviewInput!): Review } "The episodes in the Star Wars trilogy" enum Episode { "Star Wars Episode IV: A New Hope, released in 1977." NEWHOPE "Star Wars Episode V: The Empire Strikes Back, released in 1980." EMPIRE "Star Wars Episode VI: Return of the Jedi, released in 1983." JEDI } "A character from the Star Wars universe" interface Character { "The ID of the character" id: ID! "The name of the character" name: String! "The friends of the character, or an empty list if they have none" friends: [Character] "The friends of the character exposed as a connection with edges" friendsConnection(first: Int, after: ID): FriendsConnection! "The movies this character appears in" appearsIn: [Episode]! } "Units of height" enum LengthUnit { "The standard unit around the world" METER "Primarily used in the United States" FOOT } "A humanoid creature from the Star Wars universe" type Human implements Character { "The ID of the human" id: ID! "What this human calls themselves" name: String! "The home planet of the human, or null if unknown" homePlanet: String "Height in the preferred unit, default is meters" height(unit: LengthUnit = METER): Float "Mass in kilograms, or null if unknown" mass: Float "This human's friends, or an empty list if they have none" friends: [Character] "The friends of the human exposed as a connection with edges" friendsConnection(first: Int, after: ID): FriendsConnection! "The movies this human appears in" appearsIn: [Episode]! "A list of starships this person has piloted, or an empty list if none" starships: [Starship] } "An autonomous mechanical character in the Star Wars universe" type Droid implements Character { "The ID of the droid" id: ID! "What others call this droid" name: String! "This droid's friends, or an empty list if they have none" friends: [Character] "The friends of the droid exposed as a connection with edges" friendsConnection(first: Int, after: ID): FriendsConnection! "The movies this droid appears in" appearsIn: [Episode]! "This droid's primary function" primaryFunction: String } "A connection object for a character's friends" type FriendsConnection { "The total number of friends" totalCount: Int "The edges for each of the character's friends." edges: [FriendsEdge] "A list of the friends, as a convenience when edges are not needed." friends: [Character] "Information for paginating this connection" pageInfo: PageInfo! } "An edge object for a character's friends" type FriendsEdge { "A cursor used for pagination" cursor: ID! "The character represented by this friendship edge" node: Character } "Information for paginating this connection" type PageInfo { startCursor: ID endCursor: ID hasNextPage: Boolean! } "Represents a review for a movie" type Review { "The movie" episode: Episode "The number of stars this review gave, 1-5" stars: Int! "Comment about the movie" commentary: String } "The input object sent when someone is creating a new review" input ReviewInput { "0-5 stars" stars: Int! "Comment about the movie, optional" commentary: String "Favorite color, optional" favorite_color: ColorInput } "The input object sent when passing in a color" input ColorInput { red: Int! green: Int! blue: Int! } type Starship { "The ID of the starship" id: ID! "The name of the starship" name: String! "Length of the starship, along the longest axis" length(unit: LengthUnit = METER): Float coordinates: [[Float!]!] } union SearchResult = Human | Droid | Starship ================================================ FILE: juniper-from-schema/tests/schemas/returning_references.graphql ================================================ type Query { userNonNull(id: Int!): User! @juniper(ownership: "owned") userNullable(id: Int!): User @juniper(ownership: "owned") } type User { id: Int! nameNonNull: String! nameNullable: String @juniper(ownership: "owned") } schema { query: Query } ================================================ FILE: juniper-from-schema/tests/schemas/very_simple_schema.graphql ================================================ type Query { string: String! } schema { query: Query } ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/stream_item_type_not_in_subscription_field.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(infallible: true, ownership: "owned") } type User { id: ID! name: String! @juniper(stream_item_infallible: false) } schema { query: Query subscription: Subscription } } pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } pub struct Subscription; impl SubscriptionFields for Subscription { fn field_users<'r, 'a>( &self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> Box + Send + Unpin> { Box::new(juniper_from_schema::futures::stream::iter(vec![])) } } pub struct User { id: ID, name: String, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { todo!() } fn field_name(&self, _: &Executor) -> FieldResult<&String> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/stream_item_type_not_in_subscription_field.stderr ================================================ error: couldn't read $DIR/tests/subscriptions/fail/../compile_pass/setup.rs: No such file or directory (os error 2) --> $DIR/stream_item_type_not_in_subscription_field.rs:2:1 | 2 | include!("../compile_pass/setup.rs"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/stream_type_not_in_subscription_field.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(infallible: true, ownership: "owned") } type User { id: ID! name: String! @juniper(stream_type: "Foo") } schema { query: Query subscription: Subscription } } pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } pub struct Subscription; impl SubscriptionFields for Subscription { fn field_users<'r, 'a>( &self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> Box + Send + Unpin> { Box::new(juniper_from_schema::futures::stream::iter(vec![])) } } pub struct User { id: ID, name: String, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { todo!() } fn field_name(&self, _: &Executor) -> FieldResult<&String> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/stream_type_not_in_subscription_field.stderr ================================================ error: couldn't read $DIR/tests/subscriptions/fail/../compile_pass/setup.rs: No such file or directory (os error 2) --> $DIR/stream_type_not_in_subscription_field.rs:2:1 | 2 | include!("../compile_pass/setup.rs"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/subscriptions_cannot_implement_interfaces.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../compile_pass/setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription implements Entity { users: User! @juniper(infallible: true, ownership: "owned") } interface Entity { id: ID! } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } pub struct Subscription; impl SubscriptionFields for Subscription { fn field_users<'r, 'a>( &self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> Box + Send + Unpin> { Box::new(juniper_from_schema::futures::stream::iter(vec![])) } } pub struct User { id: ID, name: String, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { todo!() } fn field_name(&self, _: &Executor) -> FieldResult<&String> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/fail/subscriptions_cannot_implement_interfaces.stderr ================================================ error: couldn't read $DIR/tests/subscriptions/fail/../compile_pass/setup.rs: No such file or directory (os error 2) --> $DIR/subscriptions_cannot_implement_interfaces.rs:2:1 | 2 | include!("../compile_pass/setup.rs"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_false_stream_type_UserStream_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: false, stream_type: "UserStream", stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = FieldResult; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_false_stream_type_UserStream_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: false, stream_type: "UserStream", stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = User; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_false_stream_type_default_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: false, stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult>> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_false_stream_type_default_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: false, stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_true_stream_type_UserStream_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: true, stream_type: "UserStream", stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = FieldResult; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_true_stream_type_UserStream_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: true, stream_type: "UserStream", stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = User; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_true_stream_type_default_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: true, stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult>> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_false_async_true_stream_type_default_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: false, async: true, stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> FieldResult> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_false_stream_type_UserStream_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: false, stream_type: "UserStream", stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> UserStream { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = FieldResult; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_false_stream_type_UserStream_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: false, stream_type: "UserStream", stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> UserStream { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = User; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_false_stream_type_default_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: false, stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> BoxStream> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_false_stream_type_default_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: false, stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } impl SubscriptionFields for Subscription { fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> BoxStream { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_true_stream_type_UserStream_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: true, stream_type: "UserStream", stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> UserStream { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = FieldResult; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_true_stream_type_UserStream_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: true, stream_type: "UserStream", stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> UserStream { todo!() } } pub struct UserStream; impl Stream for UserStream { type Item = User; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context<'_>, ) -> futures::task::Poll> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_true_stream_type_default_stream_item_infallible_false.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: true, stream_item_infallible: false) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> BoxStream> { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/pass/ownership_owned_infallible_true_async_true_stream_type_default_stream_item_infallible_true.rs ================================================ #![allow(dead_code, unused_variables, unused_must_use, unused_imports)] include!("../subscription_setup.rs"); juniper_from_schema::graphql_schema! { type Query { ping: Boolean! } type Subscription { users: User! @juniper(ownership: "owned", infallible: true, async: true, stream_item_infallible: true) } type User { id: ID! name: String! } schema { query: Query subscription: Subscription } } #[async_trait] impl SubscriptionFields for Subscription { async fn field_users<'s, 'r, 'a>( &'s self, _: &Executor<'r, 'a, Context>, _: &QueryTrail<'r, User, Walked>, ) -> BoxStream { todo!() } } ================================================ FILE: juniper-from-schema/tests/subscriptions/subscription_setup.rs ================================================ include!("../compile_pass/setup.rs"); use futures::stream::Stream; use async_trait::async_trait; type BoxStream = Pin + Send>>; pub struct User { id: ID, name: String, } impl UserFields for User { fn field_id(&self, _: &Executor) -> FieldResult<&ID> { todo!() } fn field_name(&self, _: &Executor) -> FieldResult<&String> { todo!() } } pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } pub struct Subscription; ================================================ FILE: juniper-from-schema/tests/version-numbers.rs ================================================ #[macro_use] extern crate version_sync; #[test] fn test_html_root_url() { assert_html_root_url_updated!("src/lib.rs"); } ================================================ FILE: juniper-from-schema-build/Cargo.toml ================================================ [package] version = "0.5.2" authors = ["David Pedersen "] categories = ["web-programming"] description = "Use juniper-from-schema from build.rs" documentation = "https://docs.rs/juniper-from-schema-build" edition = "2018" homepage = "https://github.com/davidpdrsn/juniper-from-schema" keywords = ["web", "graphql", "juniper"] license = "MIT" name = "juniper-from-schema-build" readme = "README.md" repository = "https://github.com/davidpdrsn/juniper-from-schema.git" [dependencies] juniper-from-schema-code-gen = { version = "0.5.2", path = "../juniper-from-schema-code-gen" } syn = { version = "1", features = ["extra-traits"] } ================================================ FILE: juniper-from-schema-build/src/lib.rs ================================================ //! Use juniper-from-schema from build.rs //! //! # Example //! //! ## Required dependencies //! //! ```text //! [dependencies] //! juniper-from-schema = //! //! [build-dependencies] //! juniper-from-schema-build = //! ``` //! //! ## `build.rs` //! //! ```no_run //! fn main() -> Result<(), Box> { //! juniper_from_schema_build::compile_schema_literal(r#" //! schema { //! query: Query //! } //! //! type Query { //! ping: Boolean! //! } //! "#)?; //! //! Ok(()) //! } //! ``` //! //! ## `main.rs` or `lib.rs` //! //! ```ignore //! juniper_from_schema::include_schema!(); //! //! // the rest of your code... //! ``` #![deny( dead_code, missing_docs, mutable_borrow_reservation_conflict, unused_imports, unused_must_use, unused_variables )] #![recursion_limit = "256"] #![doc(html_root_url = "https://docs.rs/juniper-from-schema-build/0.5.2")] use std::{ env, error::Error, fs, path::{Path, PathBuf}, }; /// Simple compilation of a GraphQL schema literal. pub fn compile_schema_literal(schema: &str) -> Result<(), Box> { configure_for_schema_literal(schema).compile() } /// Configure a [`CodeGen`] with a GraphQL schema literal. /// /// [`CodeGen`]: struct.CodeGen.html pub fn configure_for_schema_literal(schema: &str) -> CodeGen { CodeGen { schema: SchemaLocation::Literal(schema.to_string()), context_type: None, error_type: None, } } /// Simple compilation of a GraphQL schema file. pub fn compile_file>(path: P) -> Result<(), Box> { configure_for_file(path).compile() } /// Configure a [`CodeGen`] with a GraphQL schema file. /// /// [`CodeGen`]: struct.CodeGen.html pub fn configure_for_file>(path: P) -> CodeGen { let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); let path = root.join(path); CodeGen { schema: SchemaLocation::File(path), context_type: None, error_type: None, } } /// GraphQL schema compiler. #[derive(Debug)] pub struct CodeGen { schema: SchemaLocation, context_type: Option>>, error_type: Option>>, } #[derive(Debug)] enum SchemaLocation { File(PathBuf), Literal(String), } impl CodeGen { /// Set the context type you want to use. /// /// Will be parsed to a Rust type using [`syn::parse_str`]. /// /// [`syn::parse_str`]: https://docs.rs/syn/1.0.48/syn/fn.parse_str.html pub fn context_type(mut self, context_type: &str) -> Self { self.context_type = Some(syn::parse_str(context_type).map_err(From::from)); self } /// Set the error type you want to use. /// /// Will be parsed to a Rust type using [`syn::parse_str`]. /// /// [`syn::parse_str`]: https://docs.rs/syn/1.0.48/syn/fn.parse_str.html pub fn error_type(mut self, error_type: &str) -> Self { self.error_type = Some(syn::parse_str(error_type).map_err(From::from)); self } /// Compile the GraphQL schema. pub fn compile(self) -> Result<(), Box> { let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("juniper_from_schema_graphql_schema.rs"); let mut code_gen = match self.schema { SchemaLocation::File(path) => { juniper_from_schema_code_gen::CodeGen::build_from_schema_file(path) } SchemaLocation::Literal(schema) => { juniper_from_schema_code_gen::CodeGen::build_from_schema_literal(schema) } }; if let Some(context_type) = self.context_type { code_gen = code_gen.context_type(context_type?); } if let Some(error_type) = self.error_type { code_gen = code_gen.error_type(error_type?); } let code = code_gen.finish().generate_code()?; fs::write(&dest_path, code.to_string())?; println!("cargo:rerun-if-changed=build.rs"); Ok(()) } } ================================================ FILE: juniper-from-schema-build-tests/basic/Cargo.toml ================================================ [package] name = "basic" version = "0.1.0" authors = ["David Pedersen "] edition = "2018" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] juniper-from-schema = { version = "0.5.2", path = "../../juniper-from-schema" } juniper = "0.15" [build-dependencies] juniper-from-schema-build = { version = "0.5.2", path = "../../juniper-from-schema-build" } ================================================ FILE: juniper-from-schema-build-tests/basic/build.rs ================================================ fn main() { juniper_from_schema_build::compile_schema_literal( r#" schema { query: Query } type Query { ping: Boolean! } "#, ) .unwrap(); } ================================================ FILE: juniper-from-schema-build-tests/basic/src/lib.rs ================================================ #![allow(unused_braces)] use juniper::{Executor, FieldResult}; juniper_from_schema::include_schema!(); #[derive(Debug)] pub struct Context; impl juniper::Context for Context {} #[derive(Debug)] pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor) -> FieldResult<&bool> { todo!() } } ================================================ FILE: juniper-from-schema-build-tests/file/Cargo.toml ================================================ [package] name = "file" version = "0.1.0" authors = ["David Pedersen "] edition = "2018" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] juniper-from-schema = { version = "0.5.2", path = "../../juniper-from-schema" } juniper = "0.15" [build-dependencies] juniper-from-schema-build = { version = "0.5.2", path = "../../juniper-from-schema-build" } ================================================ FILE: juniper-from-schema-build-tests/file/build.rs ================================================ fn main() { juniper_from_schema_build::configure_for_file("schema.graphql") .context_type("()") .error_type("MyError") .compile() .unwrap(); } ================================================ FILE: juniper-from-schema-build-tests/file/schema.graphql ================================================ schema { query: Query } type Query { ping: Boolean! } ================================================ FILE: juniper-from-schema-build-tests/file/src/lib.rs ================================================ #![allow(unused_braces)] use juniper::{Executor, FieldError, IntoFieldError}; juniper_from_schema::include_schema!(); #[derive(Debug)] pub struct Context; impl juniper::Context for Context {} #[derive(Debug)] pub struct Query; impl QueryFields for Query { fn field_ping(&self, _: &Executor<()>) -> Result<&bool, MyError> { todo!() } } #[derive(Debug)] pub struct MyError; impl IntoFieldError for MyError { fn into_field_error(self) -> FieldError { todo!() } } ================================================ FILE: juniper-from-schema-code-gen/Cargo.toml ================================================ [package] version = "0.5.2" authors = ["David Pedersen "] categories = ["web-programming"] description = "Internal code generation crate for juniper-from-schema" documentation = "https://docs.rs/juniper-from-schema-proc-macro" edition = "2018" homepage = "https://github.com/davidpdrsn/juniper-from-schema" keywords = ["web", "graphql", "juniper"] license = "MIT" name = "juniper-from-schema-code-gen" readme = "README.md" repository = "https://github.com/davidpdrsn/juniper-from-schema.git" [dependencies] syn = { version = "1", features = ["extra-traits"] } quote = "1" graphql-parser = "0.3" proc-macro2 = "1" heck = "0.3" colored = "1.8" [dev_dependencies] version-sync = "0.8" ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/code_gen_pass/gen_query_trails.rs ================================================ use super::CodeGenPass; use crate::ast_pass::{ error::ErrorKind, schema_visitor::{visit_document, SchemaVisitor}, type_name, EmitError, TypeKind, }; use graphql_parser::schema::*; use heck::{CamelCase, MixedCase, SnakeCase}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::{ collections::{HashMap, HashSet}, hash::{Hash, Hasher}, }; use syn::Ident; struct QueryTrailCodeGenPass<'pass, 'doc> { pass: &'pass mut CodeGenPass<'doc>, tokens: TokenStream, fields_map: HashMap<&'doc str, Vec<&'doc Field<'doc, &'doc str>>>, } impl<'doc> CodeGenPass<'doc> { pub fn gen_query_trails(&mut self, doc: &'doc Document<'doc, &'doc str>) -> TokenStream { let fields_map = build_fields_map(doc); let mut query_trail_pass = QueryTrailCodeGenPass { pass: self, tokens: TokenStream::new(), fields_map, }; query_trail_pass.gen_query_trail(); query_trail_pass.gen_from_default_scalar_value(); query_trail_pass.gen_from_look_ahead_value(); visit_document(&mut query_trail_pass, doc); let tokens = query_trail_pass.tokens; quote! { pub use juniper_from_schema::{Walked, NotWalked, QueryTrail}; pub use self::query_trails::*; /// `QueryTrail` extension traits specific to the GraphQL schema /// /// Generated by `juniper-from-schema`. pub mod query_trails { #![allow(unused_imports, dead_code, missing_docs)] use super::*; #tokens } } } } impl<'pass, 'doc> QueryTrailCodeGenPass<'pass, 'doc> { fn gen_query_trail(&mut self) { self.tokens.extend(quote! { use juniper_from_schema::{Walked, NotWalked, QueryTrail}; /// Convert from one type of `QueryTrail` to another. Used for converting interface and /// union trails into concrete subtypes. /// /// This trait cannot live in juniper-from-schema itself because then we wouldn't be /// able to implement it for `QueryTrail` in the user's code. That would result in /// orphan instances. /// /// Generated by `juniper-from-schema`. pub trait DowncastQueryTrail<'r, T> { /// Perform the downcast. /// /// Generated by juniper-from-schema. fn downcast(self) -> QueryTrail<'r, T, Walked>; } }) } fn gen_from_default_scalar_value(&mut self) { self.tokens.extend(quote! { /// Convert a `juniper::DefaultScalarValue` into a concrete value. /// /// This is used for `QueryTrail`. /// /// Generated by `juniper-from-schema`. pub(super) trait FromDefaultScalarValue { /// Perform the conversion. fn from(self) -> T; } }); let gen_impl = |to: &str, variant: &str| { let to = format_ident!("{}", to); let variant = format_ident!("{}", variant); quote! { impl<'a, 'b> FromDefaultScalarValue<#to> for &'a &'b juniper_from_schema::juniper::DefaultScalarValue { fn from(self) -> #to { match self { juniper_from_schema::juniper::DefaultScalarValue::#variant(x) => x.to_owned(), other => { match other { juniper_from_schema::juniper::DefaultScalarValue::Int(_) => panic!( "Failed converting scalar value. Expected `{}` got `Int`", stringify!(#to), ), juniper_from_schema::juniper::DefaultScalarValue::String(_) => panic!( "Failed converting scalar value. Expected `{}` got `String`", stringify!(#to), ), juniper_from_schema::juniper::DefaultScalarValue::Float(_) => panic!( "Failed converting scalar value. Expected `{}` got `Float`", stringify!(#to), ), juniper_from_schema::juniper::DefaultScalarValue::Boolean(_) => panic!( "Failed converting scalar value. Expected `{}` got `Boolean`", stringify!(#to), ), } } } } } } }; self.tokens.extend(gen_impl("i32", "Int")); self.tokens.extend(gen_impl("String", "String")); self.tokens.extend(gen_impl("f64", "Float")); self.tokens.extend(gen_impl("bool", "Boolean")); self.tokens.extend(quote! { impl<'a, 'b, T> FromDefaultScalarValue> for &'a &'b juniper_from_schema::juniper::DefaultScalarValue where &'a &'b juniper_from_schema::juniper::DefaultScalarValue: FromDefaultScalarValue, { fn from(self) -> Option { Some(self.from()) } } }); } fn gen_from_look_ahead_value(&mut self) { self.tokens.extend(quote! { /// Convert a `juniper::LookAheadValue` into a concrete value. /// /// This is used for `QueryTrail`. /// /// Generated by `juniper-from-schema`. pub(super) trait FromLookAheadValue { /// Perform the conversion. fn from(self) -> T; } }); let gen_scalar_impl = |to: &str| { let to = format_ident!("{}", to); quote! { impl<'a, 'b> FromLookAheadValue<#to> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> #to { match self { juniper_from_schema::juniper::LookAheadValue::Scalar(scalar) => { FromDefaultScalarValue::from(scalar) }, juniper_from_schema::juniper::LookAheadValue::Null => panic!( "Failed converting look ahead value. Expected scalar type got `null`", ), juniper_from_schema::juniper::LookAheadValue::Enum(_) => panic!( "Failed converting look ahead value. Expected scalar type got `enum`", ), juniper_from_schema::juniper::LookAheadValue::List(_) => panic!( "Failed converting look ahead value. Expected scalar type got `list`", ), juniper_from_schema::juniper::LookAheadValue::Object(_) => panic!( "Failed converting look ahead value. Expected scalar type got `object`", ), } } } } }; self.tokens.extend(gen_scalar_impl("i32")); self.tokens.extend(gen_scalar_impl("String")); self.tokens.extend(gen_scalar_impl("f64")); self.tokens.extend(gen_scalar_impl("bool")); self.tokens.extend(quote! { impl<'a, 'b, T> FromLookAheadValue> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> where &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue>: FromLookAheadValue, { fn from(self) -> Option { match self { juniper_from_schema::juniper::LookAheadValue::Null => None, other => Some(other.from()), } } } impl<'a, 'b, T> FromLookAheadValue> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> where &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue>: FromLookAheadValue, { fn from(self) -> Vec { match self { juniper_from_schema::juniper::LookAheadValue::List(values) => { values.iter().map(|value| value.from()).collect::>() }, juniper_from_schema::juniper::LookAheadValue::Scalar(_) => panic!( "Failed converting look ahead value. Expected list type got `scalar`", ), juniper_from_schema::juniper::LookAheadValue::Null => panic!( "Failed converting look ahead value. Expected list type got `null`", ), juniper_from_schema::juniper::LookAheadValue::Enum(_) => panic!( "Failed converting look ahead value. Expected list type got `enum`", ), juniper_from_schema::juniper::LookAheadValue::Object(_) => panic!( "Failed converting look ahead value. Expected list type got `object`", ), } } } impl<'a, 'b> FromLookAheadValue for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> juniper_from_schema::juniper::ID { let s = FromLookAheadValue::::from(self); juniper_from_schema::juniper::ID::new(s) } } }); if self.pass.ast_data.url_scalar_defined() { self.tokens.extend(quote! { impl<'a, 'b> FromLookAheadValue for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> url::Url { let s = FromLookAheadValue::::from(self); match url::Url::parse(&s) { Ok(url) => url, Err(e) => panic!("Error parsing URL: {}", e), } } } }); } if self.pass.ast_data.uuid_scalar_defined() { self.tokens.extend(quote! { impl<'a, 'b> FromLookAheadValue for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> uuid::Uuid { let s = FromLookAheadValue::::from(self); match uuid::Uuid::parse_str(&s) { Ok(url) => url, Err(e) => panic!("Error parsing UUID: {}", e), } } } }); } if self.pass.ast_data.date_scalar_defined() { self.tokens.extend(quote! { impl<'a, 'b> FromLookAheadValue for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> chrono::NaiveDate { let s = FromLookAheadValue::::from(self); match chrono::NaiveDate::parse_from_str(&s, "%Y-%m-%d") { Ok(date) => date, Err(e) => { panic!( "Error parsing NaiveDate. Format used is `%Y-%m-%d`\n{}", e, ) }, } } } }); } if self.pass.ast_data.date_time_scalar_defined() { self.tokens.extend(quote! { impl<'a, 'b> FromLookAheadValue> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> chrono::DateTime { let s = FromLookAheadValue::::from(self); let parsed = chrono::DateTime::parse_from_rfc3339(&s); match parsed { Ok(date_time) => date_time.into(), Err(e) => { panic!( "Error parsing DateTime. Format used is RFC 3339 (aka ISO 8601)\n{}", e, ) }, } } } impl<'a, 'b> FromLookAheadValue for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> chrono::NaiveDateTime { let s = FromLookAheadValue::::from(self); let parsed = chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S"); match parsed { Ok(date_time) => date_time.into(), Err(e) => { panic!( "Error parsing NaiveDateTime. Format used is `%Y-%m-%d %H:%M:%S`\n{}", e, ) }, } } } }); } } fn gen_field_walk_methods(&mut self, obj: InternalQueryTrailNode<'doc>) { let name = obj.name(); let trait_name = format_ident!("QueryTrail{}Extensions", obj.name()); let args_trait_name = format_ident!("QueryTrail{}ArgumentsExtensions", obj.name()); let fields = obj.fields(); let mut method_signatures = vec![]; let mut method_implementations = vec![]; let mut argument_signatures = vec![]; let mut argument_implementations = vec![]; let mut argument_types = vec![]; for field in fields { let FieldWalkMethod { method_signature, method_implementation, argument_signature, argument_implementation, argument_type, } = self.gen_field_walk_method(field, &obj); method_signatures.push(method_signature); method_implementations.push(method_implementation); argument_signatures.push(argument_signature); argument_implementations.push(argument_implementation); argument_types.push(argument_type); } self.tokens.extend(quote! { /// Extension trait for `QueryTrail` to inspect incoming queries. pub trait #trait_name<'r, K> { #(#method_signatures)* } impl<'r, K> #trait_name<'r, K> for QueryTrail<'r, #name, K> { #(#method_implementations)* } /// Extension trait for `QueryTrail` to inspect incoming query arguments. pub trait #args_trait_name<'r> { #(#argument_signatures)* } impl<'r> #args_trait_name<'r> for QueryTrail<'r, #name, juniper_from_schema::Walked> { #(#argument_implementations)* } #(#argument_types)* }); self.gen_conversion_methods(name, obj); } fn gen_conversion_methods( &mut self, original_type_name: Ident, obj: InternalQueryTrailNode<'_>, ) { let mut destination_types = vec![]; match obj { InternalQueryTrailNode::Object(_) => {} InternalQueryTrailNode::Interface(i) => { if let Some(i) = &self.pass.ast_data.get_implementors_of_interface(&i.name) { for interface_implementor_name in *i { let ident = format_ident!("{}", interface_implementor_name); destination_types.push(ident); } } } InternalQueryTrailNode::Union(u, _) => { for type_ in &u.types { let ident = format_ident!("{}", type_); destination_types.push(ident); } } } for type_ in destination_types { self.tokens.extend(quote! { impl<'r> DowncastQueryTrail<'r, #type_> for &QueryTrail<'r, #original_type_name, Walked> { fn downcast(self) -> QueryTrail<'r, #type_, Walked> { QueryTrail { look_ahead: self.look_ahead, node_type: std::marker::PhantomData, walked: juniper_from_schema::Walked, } } } }); } } fn error_msg_if_field_types_dont_overlap(&mut self, union: &'doc UnionType<'doc, &'doc str>) { let fields_map = &self.fields_map; let mut prev: HashMap<&'doc str, (&'doc str, &'doc str)> = HashMap::new(); for type_b in &union.types { if let Some(fields) = fields_map.get(type_b) { for field in fields { let field_type_b = type_name(&field.field_type); if let Some((type_a, field_type_a)) = prev.get(&field.name) { if field_type_b != *field_type_a { self.pass.emit_error( union.position, ErrorKind::UnionFieldTypeMismatch { union_name: union.name.to_string(), field_name: field.name.to_string(), type_a: type_a.to_string(), type_b: type_b.to_string(), field_type_a: field_type_a.to_string(), field_type_b: field_type_b.to_string(), }, ); } } prev.insert(&field.name, (type_b, field_type_b)); } } } } fn gen_field_walk_method( &mut self, field: &'doc Field<'doc, &'doc str>, obj: &InternalQueryTrailNode, ) -> FieldWalkMethod { let field_type = type_name(&field.field_type); let ty = self .pass .graphql_type_to_rust_type(&field.field_type, false, field.position); let field_type = format_ident!("{}", field_type.to_camel_case()); match ty.kind() { TypeKind::Scalar => { let name = format_ident!("{}", &field.name.to_snake_case()); let string_name = &field.name.to_mixed_case(); let method_signature = quote! { /// Check if a scalar leaf node is queried for /// /// Generated by `juniper-from-schema`. fn #name(&self) -> bool; }; let method_implementation = quote! { fn #name(&self) -> bool { use juniper_from_schema::juniper::LookAheadMethods; self.look_ahead .map(|la| { la.children() .iter() .any(|child| child.field_name() == #string_name) }) .unwrap_or(false) } }; let (argument_signature, argument_implementation, argument_type) = self.gen_args_query_trail(field, &name, obj); FieldWalkMethod { method_signature, method_implementation, argument_signature, argument_implementation, argument_type, } } TypeKind::Type => { let name = format_ident!("{}", &field.name.to_snake_case()); let string_name = &field.name.to_mixed_case(); let method_signature = quote! { /// Walk the trail into a field. /// /// Generated by `juniper-from-schema`. fn #name(&self) -> QueryTrail<'r, #field_type, juniper_from_schema::NotWalked>; }; let method_implementation = quote! { fn #name(&self) -> QueryTrail<'r, #field_type, juniper_from_schema::NotWalked> { use juniper_from_schema::juniper::LookAheadMethods; let child = self.look_ahead.and_then(|la| { la.children().into_iter().find(|child| { child.field_name() == #string_name }) }); QueryTrail { look_ahead: child, node_type: std::marker::PhantomData, walked: juniper_from_schema::NotWalked, } } }; let (argument_signature, argument_implementation, argument_type) = self.gen_args_query_trail(field, &name, obj); FieldWalkMethod { method_signature, method_implementation, argument_signature, argument_implementation, argument_type, } } } } fn gen_args_query_trail( &mut self, field: &'doc Field<'doc, &'doc str>, name: &Ident, obj: &InternalQueryTrailNode, ) -> (TokenStream, TokenStream, TokenStream) { let mut argument_signature = quote! {}; let mut argument_implementation = quote! {}; let mut argument_type = quote! {}; let obj_type = obj.name(); let args_method_name = format_ident!("{}_args", name); if field.arguments.is_empty() { argument_signature.extend(quote! { /// Inspect argument in incoming query. /// /// This field takes no arguments, so therefore it returns `()`. #[allow(clippy::unused_unit)] fn #args_method_name(&self) -> (); }); argument_implementation.extend(quote! { #[allow(missing_docs)] #[allow(clippy::unused_unit)] #[inline] fn #args_method_name(&self) -> () { () } }); } else { let args_type_name = format_ident!("{}{}Args", obj.name(), name.to_string().to_camel_case()); argument_signature.extend(quote! { /// Inspect argument in incoming query. fn #args_method_name(&'r self) -> #args_type_name<'r>; }); argument_implementation.extend(quote! { #[allow(missing_docs)] fn #args_method_name(&'r self) -> #args_type_name<'r> { #args_type_name(self) } }); let arguments_methods = field .arguments .iter() .map(|input_value| self.gen_argument_look_ahead_methods(input_value, &field.name)); argument_type.extend(quote! { /// This is used for inspecting arguments to a field. /// /// Generated by `juniper-from-schema`. pub struct #args_type_name<'r>( &'r QueryTrail<'r, #obj_type, juniper_from_schema::Walked> ); impl<'r> #args_type_name<'r> { #(#arguments_methods)* } }); } (argument_signature, argument_implementation, argument_type) } fn gen_argument_look_ahead_methods( &mut self, input_value: &'doc InputValue<'doc, &'doc str>, field_name: &str, ) -> TokenStream { let default_value = input_value.default_value.as_ref().map(|value| { self.pass.quote_value( &value, type_name(&input_value.value_type), input_value.position, ) }); let mut field_type = &self.pass.graphql_type_to_rust_type( &input_value.value_type, false, input_value.position, ); if default_value.is_some() { field_type = field_type.remove_one_layer_of_nullability(); } let name = &input_value.name; let ident = format_ident!("{}", name.to_snake_case()); if let Some(default_value) = default_value { quote! { #[allow(missing_docs)] pub fn #ident(&self) -> #field_type { use juniper_from_schema::juniper::LookAheadMethods; // these `expect`s are fine since these methods you can only obtain // arguments from walked query trails let lh = &self .0 .look_ahead .expect("look_ahead") .children() .into_iter() .find(|child| child.field_name() == #field_name) .expect("select child"); let arg = lh.arguments().iter().find(|arg| { arg.name() == #name }); if let Some(arg) = arg { let value = arg.value(); FromLookAheadValue::<#field_type>::from(value) } else { #default_value } } } } else { quote! { #[allow(missing_docs)] pub fn #ident(&self) -> #field_type { use juniper_from_schema::juniper::LookAheadMethods; // these `expect`s are fine since these methods you can only obtain // arguments from walked query trails let lh = &self .0 .look_ahead .expect("look_ahead") .children() .into_iter() .find(|child| child.field_name() == #field_name) .expect("select child"); let arg = lh.arguments().iter().find(|arg| { arg.name() == #name }).expect("no argument with name"); let value = arg.value(); FromLookAheadValue::<#field_type>::from(value) } } } } } impl<'pass, 'doc> SchemaVisitor<'doc> for QueryTrailCodeGenPass<'pass, 'doc> { fn visit_object_type(&mut self, obj: &'doc ObjectType<'doc, &'doc str>) { self.gen_field_walk_methods(InternalQueryTrailNode::Object(obj)); } fn visit_interface_type(&mut self, interface: &'doc InterfaceType<'doc, &'doc str>) { self.gen_field_walk_methods(InternalQueryTrailNode::Interface(interface)) } fn visit_union_type(&mut self, union: &'doc UnionType<'doc, &'doc str>) { self.error_msg_if_field_types_dont_overlap(union); self.gen_field_walk_methods(InternalQueryTrailNode::Union( union, build_union_fields_set(union, &self.fields_map), )) } } struct FieldWalkMethod { method_signature: TokenStream, method_implementation: TokenStream, argument_signature: TokenStream, argument_implementation: TokenStream, argument_type: TokenStream, } #[derive(Clone, Debug)] struct HashFieldByName<'a>(&'a Field<'a, &'a str>); impl<'a> PartialEq for HashFieldByName<'a> { fn eq(&self, other: &HashFieldByName) -> bool { self.0.name == other.0.name } } impl<'a> Eq for HashFieldByName<'a> {} impl<'a> Hash for HashFieldByName<'a> { fn hash(&self, state: &mut H) { self.0.name.hash(state); } } #[derive(Debug)] enum InternalQueryTrailNode<'a> { Object(&'a ObjectType<'a, &'a str>), Interface(&'a InterfaceType<'a, &'a str>), Union(&'a UnionType<'a, &'a str>, HashSet>), } impl<'a> InternalQueryTrailNode<'a> { fn name(&self) -> Ident { match self { InternalQueryTrailNode::Object(inner) => format_ident!("{}", inner.name), InternalQueryTrailNode::Interface(inner) => format_ident!("{}", inner.name), InternalQueryTrailNode::Union(inner, _fields) => format_ident!("{}", inner.name), } } fn fields(&self) -> Vec<&'a Field<'a, &'a str>> { match self { InternalQueryTrailNode::Object(inner) => inner.fields.iter().collect(), InternalQueryTrailNode::Interface(inner) => inner.fields.iter().collect(), InternalQueryTrailNode::Union(_inner, fields) => fields .iter() .map(|hashable_field| hashable_field.0) .collect(), } } } fn build_union_fields_set<'d>( union: &UnionType<'d, &'d str>, fields_map: &HashMap<&'d str, Vec<&'d Field<'d, &'d str>>>, ) -> HashSet> { let mut union_fields_set = HashSet::new(); for type_ in &union.types { if let Some(fields) = fields_map.get(type_) { for field in fields { union_fields_set.insert(HashFieldByName(&field)); } } } union_fields_set } fn build_fields_map<'a>( doc: &'a Document<'a, &'a str>, ) -> HashMap<&'a str, Vec<&'a Field<'a, &'a str>>> { let mut map: HashMap<&'a str, Vec<&'a Field<'a, &'a str>>> = HashMap::new(); for def in &doc.definitions { if let Definition::TypeDefinition(type_def) = def { if let TypeDefinition::Object(obj) = type_def { for field in &obj.fields { let entry = map.entry(&obj.name).or_insert_with(Vec::new); entry.push(field); } } } } map } #[cfg(test)] mod test { use super::*; use crate::ast_pass::AstData; #[test] fn test_fails_to_generate_query_trail_for_unions_where_fields_dont_overlap() { let schema = r#" union Entity = User | Company type User { country: Country! } type Company { country: OtherCountry! } type Country { id: Int! } type OtherCountry { id: Int! } "#; let doc = graphql_parser::parse_schema(&schema).unwrap(); let ast_data = AstData::new_from_doc(&doc).unwrap(); let context_type = crate::default_error_type(); let error_type = crate::default_context_type(); let mut out = CodeGenPass::new(schema, &context_type, &error_type, ast_data); out.gen_query_trails(&doc); assert_eq!(1, out.errors.len()); } } ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/code_gen_pass/mod.rs ================================================ mod gen_query_trails; use super::{ directive_parsing::*, error::Error, schema_visitor::*, type_name, validations::*, AstData, DateTimeScalarDefinition, EmitError, ErrorKind, NullableType, TypeKind, }; use graphql_parser::{schema, schema::Value, Pos}; use heck::{CamelCase, SnakeCase}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::{ collections::{BTreeMap, BTreeSet, HashSet}, convert::TryFrom, }; use syn::{parse_quote, Ident, LitStr, Token}; #[derive(Debug)] pub struct CodeGenPass<'doc> { error_type: &'doc syn::Type, context_type: &'doc syn::Type, errors: BTreeSet, ast_data: AstData<'doc>, raw_schema: &'doc str, scalars: Vec>, objects: Vec>, subscription: Option>, interfaces: Vec>, unions: Vec>, enums: Vec>, input_objects: Vec>, schema_type: Option, } impl<'doc> CodeGenPass<'doc> { pub fn new( raw_schema: &'doc str, error_type: &'doc syn::Type, context_type: &'doc syn::Type, ast_data: AstData<'doc>, ) -> Self { Self { error_type, context_type, ast_data, errors: BTreeSet::new(), raw_schema, scalars: Vec::new(), objects: Vec::new(), subscription: None, interfaces: Vec::new(), unions: Vec::new(), enums: Vec::new(), input_objects: Vec::new(), schema_type: None, } } pub fn gen_juniper_code( mut self, doc: &'doc schema::Document<'doc, &'doc str>, ) -> Result> { self.validate_doc(doc); self.check_for_errors()?; let query_trail_tokens = self.gen_query_trails(doc); visit_document(&mut self, doc); self.check_for_errors()?; let Self { scalars, objects, subscription, interfaces, unions, enums, input_objects, schema_type, error_type: _, context_type: _, errors: _, ast_data: _, raw_schema: _, } = self; let mut tokens = quote! { #(#scalars)* #(#objects)* #subscription #(#interfaces)* #(#unions)* #(#enums)* #(#input_objects)* #schema_type }; // eprintln!("\n"); // eprintln!("{}", tokens); // eprintln!("\n"); tokens.extend(query_trail_tokens); Ok(tokens) } fn validate_doc(&mut self, doc: &'doc schema::Document<'doc, &'doc str>) { let mut validation_visitor = FieldNameCaseValidator::new().and(UuidNameCaseValidator::new()); visit_document(&mut validation_visitor, doc); let (field_validator, uuid_name_validator) = validation_visitor.into_inner(); for error in field_validator .errors .into_iter() .chain(uuid_name_validator.errors) { self.errors.insert(error); } } fn check_for_errors(&self) -> Result<(), BTreeSet> { if self.errors.is_empty() { Ok(()) } else { Err(self.errors.clone()) } } } impl<'doc> EmitError for CodeGenPass<'doc> { fn emit_error(&mut self, pos: Pos, kind: ErrorKind) { self.errors.emit_error(pos, kind) } } impl<'doc> SchemaVisitor<'doc> for CodeGenPass<'doc> { fn visit_schema_definition(&mut self, node: &'doc schema::SchemaDefinition<'doc, &'doc str>) { let schema::SchemaDefinition { position, directives: _, query, mutation, subscription, } = node; let query_type = match query { Some(query) => { let ident = format_ident!("{}", query); parse_quote! { #ident } } None => { self.emit_error(*position, ErrorKind::NoQueryType); return; } }; let mutation_type = match mutation { Some(mutation) => { let ident = format_ident!("{}", mutation); parse_quote! { #ident } } None => { let context_type = &self.context_type; parse_quote! { juniper_from_schema::juniper::EmptyMutation<#context_type> } } }; let subscription_type = match subscription { Some(subscription) => { let ident = format_ident!("{}", subscription); parse_quote! { #ident } } None => { let context_type = &self.context_type; parse_quote! { juniper_from_schema::juniper::EmptySubscription<#context_type> } } }; self.schema_type = Some(SchemaType { query_type, mutation_type, subscription_type, }); } fn visit_directive_definition( &mut self, node: &'doc schema::DirectiveDefinition<'doc, &'doc str>, ) { if node.name == "juniper" { self.validate_juniper_directive_definition(node) } } fn visit_scalar_type(&mut self, node: &'doc schema::ScalarType<'doc, &'doc str>) { match &*node.name { name if name == crate::DATE_TIME_SCALAR_NAME => { // This case is special because it supports a directive. We don't need to parse and // check the it though that is done by `AstData::visit_scalar_type` if node.description.is_some() { self.emit_error(node.position, ErrorKind::SpecialCaseScalarWithDescription); } } name if name == crate::DATE_SCALAR_NAME || name == crate::URL_SCALAR_NAME || name == crate::UUID_SCALAR_NAME => { let () = self.parse_directives(node); if node.description.is_some() { self.emit_error(node.position, ErrorKind::SpecialCaseScalarWithDescription); } } _ => { let schema::ScalarType { position, description, name, directives: _, } = node; let () = self.parse_directives(node); match &**name { "String" | "Float" | "Int" | "Boolean" | "ID" => { self.emit_error(*position, ErrorKind::CannotDeclareBuiltinAsScalar); } _ => {} } self.scalars.push(Scalar { name: format_ident!("{}", name), description: description.as_ref(), }); } }; } fn visit_object_type(&mut self, node: &'doc schema::ObjectType<'doc, &'doc str>) { let schema::ObjectType { position, description, name, implements_interfaces, directives: _, fields, } = node; let () = self.parse_directives(node); if self.ast_data.is_subscription_type(name) { if !implements_interfaces.is_empty() { self.emit_error(*position, ErrorKind::SubscriptionsCannotImplementInterfaces); } let fields = fields .iter() .map(|field| self.graphql_field_to_rust_field(field, FieldLocation::Subscription)) .collect(); self.subscription = Some(Subscription { name: format_ident!("{}", name), description: description.as_ref(), context_type: self.context_type, fields, }); } else { let fields = fields .iter() .map(|field| self.graphql_field_to_rust_field(field, FieldLocation::Object)) .collect(); let implements_interfaces = implements_interfaces .iter() .map(|name| format_ident!("{}", name)) .collect(); self.objects.push(Object { name: format_ident!("{}", name), description: description.as_ref(), context_type: self.context_type, fields, implements_interfaces, }); } } fn visit_interface_type(&mut self, node: &'doc schema::InterfaceType<'doc, &'doc str>) { let schema::InterfaceType { description, name, fields, position: _, directives: _, } = node; let () = self.parse_directives(node); let implementors = self .ast_data .get_implementors_of_interface(name) .cloned() .unwrap_or_else(Vec::new) .into_iter() .map(|name| format_ident!("{}", name)) .collect::>(); let name = format_ident!("{}", name); let fields = fields .iter() .map(|field| self.graphql_field_to_rust_field(field, FieldLocation::Interface)) .collect(); self.interfaces.push(Interface { description: description.as_ref(), trait_name: format_ident!("{}Interface", name), name, fields, implementors, context_type: self.context_type, }); } fn visit_union_type(&mut self, node: &'doc schema::UnionType<'doc, &'doc str>) { let schema::UnionType { position, description, name, types, directives: _, } = node; let () = self.parse_directives(node); let name = format_ident!("{}", name); let variants = types .iter() .map(|variant_name| { let graphql_type: schema::Type<'doc, &'doc str> = schema::Type::NamedType(*variant_name); let type_inside = self .graphql_type_to_rust_type(&graphql_type, false, *position) .remove_one_layer_of_nullability_by_value(); let ident = format_ident!("{}", variant_name); UnionVariant { graphql_name: ident.clone(), rust_name: ident, type_inside, } }) .collect::>(); self.unions.push(Union { name, variants, description: description.as_ref(), context_type: self.context_type, }) } fn visit_enum_type(&mut self, node: &'doc schema::EnumType<'doc, &'doc str>) { let schema::EnumType { description, name, values, position: _, directives: _, } = node; let () = self.parse_directives(node); let name = format_ident!("{}", name); let variants = values .iter() .map(|value| { let schema::EnumValue { name, description, position: _, directives: _, } = value; let graphql_name = *name; let name = format_ident!("{}", name.to_camel_case()); let deprecation = self.parse_directives(value); EnumVariant { name, deprecation, description: description.as_ref(), graphql_name, } }) .collect(); self.enums.push(Enum { name, variants, description: description.as_ref(), }) } fn visit_input_object_type(&mut self, node: &'doc schema::InputObjectType<'doc, &'doc str>) { let schema::InputObjectType { description, name, fields, position: _, directives: _, } = node; let () = self.parse_directives(node); let name = format_ident!("{}", name); let fields = fields .iter() .map(|field| { let schema::InputValue { description, name, value_type, default_value, position, directives: _, } = field; let () = self.parse_directives(field); if default_value.is_some() { self.emit_error(*position, ErrorKind::InputTypeFieldWithDefaultValue); } let ty = self.graphql_type_to_rust_type(value_type, false, *position); let name = format_ident!("{}", name.to_snake_case()); InputObjectField { name, ty, description: description.as_ref(), } }) .collect::>(); self.input_objects.push(InputObject { name, description: description.as_ref(), fields, }); } fn visit_scalar_type_extension( &mut self, inner: &'doc schema::ScalarTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } fn visit_object_type_extension( &mut self, inner: &'doc schema::ObjectTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } fn visit_interface_type_extension( &mut self, inner: &'doc schema::InterfaceTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } fn visit_union_type_extension( &mut self, inner: &'doc schema::UnionTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } fn visit_enum_type_extension( &mut self, inner: &'doc schema::EnumTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } fn visit_input_object_type_extension( &mut self, inner: &'doc schema::InputObjectTypeExtension<'doc, &'doc str>, ) { self.emit_error(inner.position, ErrorKind::TypeExtensionNotSupported) } } impl<'doc> CodeGenPass<'doc> { fn graphql_field_to_rust_field( &mut self, field: &'doc schema::Field<'doc, &'doc str>, field_location: FieldLocation, ) -> Field<'doc> { let schema::Field { position, description, name, arguments, field_type, directives: _, } = field; let field_directives = self.parse_directives(field); self.validate_directive_for_field(&field_directives, field_location, *position); let args = arguments .iter() .map(|arg| { let schema::InputValue { position, description, name, value_type, default_value, directives: _, } = arg; let () = self.parse_directives(arg); let default_value = default_value .as_ref() .map(|v| self.quote_value(v, type_name(value_type), *position)); let ty = self.graphql_type_to_rust_type(value_type, false, *position); if default_value.is_some() && !ty.is_nullable() { self.emit_error(*position, ErrorKind::NonnullableFieldWithDefaultValue); } let name_without_raw_ident = format_ident!("{}", name.to_snake_case()); FieldArg { name: format_ident!("r#{}", name_without_raw_ident), name_without_raw_ident, description: description.as_ref(), ty, default_value, } }) .collect(); let return_type = self.graphql_type_to_rust_type( field_type, field_directives.ownership.is_as_ref(), *position, ); if field_directives.ownership == Ownership::AsRef && !return_type.supports_as_ref() { self.emit_error(*position, ErrorKind::AsRefOwnershipForNamedType); } Field { description: description.as_ref(), name: format_ident!("r#{}", name.to_snake_case()), context_type: self.context_type, error_type: self.error_type, args, return_type, directives: field_directives, } } fn graphql_type_to_rust_type( &mut self, graphql_type: &schema::Type<'doc, &'doc str>, as_ref: bool, pos: Pos, ) -> Type { fn gen_leaf<'doc>(pass: &CodeGenPass<'doc>, name: &'doc str) -> Type { match &*name { "String" => Type::Scalar(Either::A(parse_quote! { std::string::String })), "Float" => Type::Scalar(Either::A(parse_quote! { f64 })), "Int" => Type::Scalar(Either::A(parse_quote! { i32 })), "Boolean" => Type::Scalar(Either::A(parse_quote! { bool })), "ID" => Type::Scalar(Either::A(parse_quote! { juniper_from_schema::juniper::ID })), name => { if pass.ast_data.is_scalar(name) { Type::Scalar(Either::B(format_ident!("{}", name))) } else if pass.ast_data.is_enum_type(name) { Type::Enum(format_ident!("{}", name)) } else if pass.ast_data.is_union_type(name) { Type::Union(format_ident!("{}", name)) } else if pass.ast_data.is_interface_type(name) { Type::Interface(format_ident!("{}", name)) } else { Type::Object(format_ident!("{}", name)) } } } } fn gen_node<'doc>( pass: &mut CodeGenPass<'doc>, ty: &NullableType<'doc>, as_ref: bool, pos: Pos, ) -> Type { match ty { NullableType::NamedType(inner) => match &**inner { name if name == crate::URL_SCALAR_NAME => { if !pass.ast_data.url_scalar_defined() { pass.emit_error(pos, ErrorKind::UrlScalarNotDefined); } Type::Scalar(Either::A(parse_quote! { url::Url })) } name if name == crate::UUID_SCALAR_NAME => { if !pass.ast_data.uuid_scalar_defined() { pass.emit_error(pos, ErrorKind::UuidScalarNotDefined); } Type::Scalar(Either::A(parse_quote! { uuid::Uuid })) } name if name == crate::DATE_SCALAR_NAME => { if !pass.ast_data.date_scalar_defined() { pass.emit_error(pos, ErrorKind::DateScalarNotDefined); } Type::Scalar(Either::A(parse_quote! { chrono::naive::NaiveDate })) } name if name == crate::DATE_TIME_SCALAR_NAME => { match pass.ast_data.date_time_scalar_definition() { Some(DateTimeScalarDefinition::WithTimeZone) => Type::Scalar( Either::A(parse_quote! { chrono::DateTime }), ), Some(DateTimeScalarDefinition::WithoutTimeZone) => Type::Scalar( Either::A(parse_quote! { chrono::naive::NaiveDateTime }), ), None => { pass.emit_error(pos, ErrorKind::DateTimeScalarNotDefined); Type::Scalar(Either::A( parse_quote! { chrono::DateTime }, )) } } } _ => gen_leaf(pass, inner), }, NullableType::ListType(inner) => { if as_ref { Type::List(Box::new(Type::Ref(Box::new(gen_node( pass, &*inner, false, pos, ))))) } else { Type::List(Box::new(gen_node(pass, &*inner, false, pos))) } } NullableType::NullableType(inner) => { if as_ref { Type::Nullable(Box::new(Type::Ref(Box::new(gen_node( pass, &*inner, false, pos, ))))) } else { Type::Nullable(Box::new(gen_node(pass, &*inner, false, pos))) } } } } let nullable_type = NullableType::from_schema_type(graphql_type); gen_node(self, &nullable_type, as_ref, pos) } fn quote_value( &mut self, value: &'doc Value<'doc, &'doc str>, type_name: &'doc str, pos: Pos, ) -> TokenStream { match value { Value::Float(inner) => quote! { #inner }, Value::Int(inner) => { let number = inner .as_i64() .expect("failed to convert default number argument to i64"); let number = i32::try_from(number) .expect("failed to convert default number argument to i64"); quote! { #number } } Value::String(inner) => quote! { #inner.to_string() }, Value::Boolean(inner) => quote! { #inner }, Value::Enum(variant_name) => { let type_name = format_ident!("{}", type_name.to_camel_case()); let variant_name = format_ident!("{}", variant_name.to_camel_case()); quote! { #type_name::#variant_name } } Value::List(list) => { let mut acc = quote! { let mut vec = Vec::new(); }; for value in list { let value_quoted = self.quote_value(value, type_name, pos); acc.extend(quote! { vec.push(#value_quoted); }); } acc.extend(quote! { vec }); quote! { { #acc } } } Value::Object(map) => self.quote_object_value(map, type_name, pos), Value::Variable(_) => { self.emit_error(pos, ErrorKind::VariableDefaultValue); quote! {} } Value::Null => quote! { None }, } } fn quote_object_value( &mut self, map: &'doc BTreeMap<&'doc str, Value<'doc, &'doc str>>, type_name: &'doc str, pos: Pos, ) -> TokenStream { let name = format_ident!("{}", type_name); let mut fields_seen: HashSet<&'doc str> = HashSet::new(); // Set fields given in `map` let mut field_assigments = map .iter() .map(|(key, value)| { fields_seen.insert(key); let field_name = format_ident!("{}", key.to_snake_case()); let field_type_name = self .ast_data .input_object_field_type_name(&type_name, &key) .unwrap_or_else(|| { panic!("input_object_field_type_name {} {}", type_name, key) }); let value_quote = self.quote_value(value, field_type_name, pos); match self .ast_data .input_object_field_is_nullable(&type_name, &key) { Some(true) | None => { if value == &Value::Null { quote! { #field_name: #value_quote } } else { quote! { #field_name: Some(#value_quote) } } } Some(false) => quote! { #field_name: #value_quote }, } }) .collect::>(); // Set fields not given in map to `None` if let Some(fields) = self.ast_data.input_object_field_names(&type_name) { for field_name in fields { if !fields_seen.contains(field_name) { let field_name = format_ident!("{}", field_name.to_snake_case()); field_assigments.push(quote! { #field_name: None }); } } } let tokens = quote! { #name { #(#field_assigments),*, } }; quote! { { #tokens } } } fn validate_juniper_directive_definition( &mut self, directive: &'doc schema::DirectiveDefinition<'doc, &'doc str>, ) { use schema::{DirectiveLocation, InputValue, Type as GraphqlType}; assert_eq!(directive.name, "juniper"); let mut field_location_present = false; let mut scalar_location_present = false; for location in directive.locations.iter() { match location { DirectiveLocation::FieldDefinition => { field_location_present = true; } DirectiveLocation::Scalar => { scalar_location_present = true; } other => self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( format!( "Invalid location for @juniper directive: `{}`", other.as_str() ), Some("Location must be `FIELD_DEFINITION | SCALAR`".to_string()), ), ), } } if !field_location_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing `FIELD_DEFINITION` directive location for @juniper directive" .to_string(), Some("Location must be `FIELD_DEFINITION | SCALAR`".to_string()), ), ) } if !scalar_location_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing `SCALAR` directive location for @juniper directive".to_string(), Some("Location must be `FIELD_DEFINITION | SCALAR`".to_string()), ), ) } let no_directives = |this: &mut Self, arg: &InputValue<'doc, &'doc str>, name: &str| { for dir in arg.directives.iter() { this.emit_error( dir.position, ErrorKind::InvalidJuniperDirective( format!("`{}` argument doesn't support directives", name), None, ), ) } }; let of_type = |this: &mut Self, arg: &InputValue<'doc, &'doc str>, ty: GraphqlType<'doc, &'doc str>, name: &str| { if arg.value_type != ty { this.emit_error( arg.position, ErrorKind::InvalidJuniperDirective( format!("`{}` argument must have type `{}`", name, ty), Some(format!("Got `{}`", arg.value_type)), ), ) } }; let default_value = |this: &mut Self, arg: &InputValue<'doc, &'doc str>, value: Value<'doc, &'doc str>, name: &str| { if let Some(default) = &arg.default_value { if default == &value { // ok } else { this.emit_error( arg.position, ErrorKind::InvalidJuniperDirective( format!( "Invalid default value for `{}` argument. Must be `{}`", name, value ), Some(format!("Got `{}`", default)), ), ) } } else { this.emit_error( arg.position, ErrorKind::InvalidJuniperDirective( format!( "Missing default value for `{}` argument. Must be `{}`", name, value ), None, ), ) } }; let mut ownership_present = false; let mut infallible_present = false; let mut with_time_zone_present = false; let mut async_present = false; let mut stream_item_infallible_present = false; let mut stream_type_present = false; for arg in directive.arguments.iter() { match arg.name { name @ "ownership" => { ownership_present = true; of_type(self, arg, GraphqlType::NamedType("String"), name); no_directives(self, arg, name); default_value(self, arg, Value::String("borrowed".to_string()), name); } name @ "infallible" => { infallible_present = true; of_type(self, arg, GraphqlType::NamedType("Boolean"), name); no_directives(self, arg, name); default_value(self, arg, Value::Boolean(false), name); } name @ "with_time_zone" => { with_time_zone_present = true; of_type(self, arg, GraphqlType::NamedType("Boolean"), name); no_directives(self, arg, name); default_value(self, arg, Value::Boolean(true), name); } name @ "async" => { async_present = true; of_type(self, arg, GraphqlType::NamedType("Boolean"), name); no_directives(self, arg, name); default_value(self, arg, Value::Boolean(false), name); } name @ "stream_item_infallible" => { stream_item_infallible_present = true; of_type(self, arg, GraphqlType::NamedType("Boolean"), name); no_directives(self, arg, name); default_value(self, arg, Value::Boolean(true), name); } name @ "stream_type" => { stream_type_present = true; of_type(self, arg, GraphqlType::NamedType("String"), name); no_directives(self, arg, name); default_value(self, arg, Value::Null, name); } name => { self.emit_error( arg.position, ErrorKind::InvalidJuniperDirective( format!("Invalid argument for @juniper directive: `{}`", name), Some( "Supported arguments are `ownership`, `infallible`, `with_time_zone`, `async`, `stream_item_infallible`, and `stream_type`".to_string() ), ), ) } } } if !ownership_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing argument `ownership`".to_string(), None, ), ) } if !infallible_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing argument `infallible`".to_string(), None, ), ) } if !with_time_zone_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing argument `with_time_zone`".to_string(), None, ), ) } if !async_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective("Missing argument `async`".to_string(), None), ) } if !stream_item_infallible_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing argument `stream_item_infallible`".to_string(), None, ), ) } if !stream_type_present { self.emit_error( directive.position, ErrorKind::InvalidJuniperDirective( "Missing argument `stream_type`".to_string(), None, ), ) } } fn validate_directive_for_field( &mut self, directives: &FieldDirectives, field_location: FieldLocation, pos: Pos, ) { match field_location { FieldLocation::Object | FieldLocation::Interface => { if directives.stream_type.is_some() { self.emit_error(pos, ErrorKind::StreamTypeNotSupportedHere); } if directives.stream_item_infallible.is_some() { self.emit_error(pos, ErrorKind::StreamItemInfallibleNotSupportedHere); } } FieldLocation::Subscription => { match &directives.ownership { Ownership::Borrowed | Ownership::AsRef => { self.emit_error(pos, ErrorKind::SubscriptionFieldMustBeOwned); } Ownership::Owned => {} } if let Some(ty) = &directives.stream_type { if let Err(err) = syn::parse_str::(&ty.value) { self.emit_error(pos, ErrorKind::InvalidStreamReturnType(err.to_string())); } } } } } } #[derive(Debug, Clone)] enum Either { A(A), B(B), } impl ToTokens for Either where A: ToTokens, B: ToTokens, { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Either::A(a) => a.to_tokens(tokens), Either::B(b) => b.to_tokens(tokens), } } } #[derive(Debug, Clone)] enum Type { Scalar(Either), Enum(Ident), Union(Ident), Interface(Ident), Object(Ident), Ref(Box), List(Box), Nullable(Box), } impl Type { fn is_nullable(&self) -> bool { matches!(self, Type::Nullable(_)) } fn supports_as_ref(&self) -> bool { match self { Type::Scalar(_) => false, Type::Enum(_) => false, Type::Union(_) => false, Type::Interface(_) => false, Type::Object(_) => false, Type::Ref(_) => false, Type::List(_) => true, Type::Nullable(_) => true, } } fn remove_one_layer_of_nullability_by_value(self) -> Box { match self { Type::Nullable(inner) => inner, other => Box::new(other), } } fn remove_one_layer_of_nullability(&self) -> &Type { match self { Type::Nullable(inner) => inner, other => other, } } fn kind(&self) -> TypeKind { match self { Type::Scalar(_) => TypeKind::Scalar, Type::Enum(_) => TypeKind::Scalar, Type::Union(_) => TypeKind::Type, Type::Object(_) => TypeKind::Type, Type::Interface { .. } => TypeKind::Type, Type::Ref(inner) => inner.kind(), Type::List(inner) => inner.kind(), Type::Nullable(inner) => inner.kind(), } } fn innermost_type(&self) -> &Type { match self { Type::Scalar(_) => self, Type::Enum(_) => self, Type::Union(_) => self, Type::Object(_) => self, Type::Interface { .. } => self, Type::Ref(inner) => inner.innermost_type(), Type::List(inner) => inner.innermost_type(), Type::Nullable(inner) => inner.innermost_type(), } } } impl ToTokens for Type { fn to_tokens(&self, tokens: &mut TokenStream) { let code = match self { Type::Scalar(inner) => { quote! { #inner } } Type::Enum(inner) => { quote! { #inner } } Type::Union(inner) => { quote! { #inner } } Type::Object(inner) => { quote! { #inner } } Type::Interface(inner) => { quote! { #inner } } Type::Ref(inner) => { quote! { &#inner } } Type::List(inner) => { quote! { std::vec::Vec<#inner> } } Type::Nullable(inner) => { quote! { std::option::Option<#inner> } } }; tokens.extend(code); } } #[derive(Debug, Default)] struct Output {} #[derive(Debug)] struct Scalar<'doc> { name: Ident, description: Option<&'doc String>, } impl<'doc> ToTokens for Scalar<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Scalar { name, description } = self; let attrs = if let Some(description) = description { quote! { #[derive(juniper_from_schema::juniper::GraphQLScalarValue)] #[graphql( transparent, description = #description, )] } } else { quote! { #[derive(juniper_from_schema::juniper::GraphQLScalarValue)] #[graphql(transparent)] } }; let code = quote! { #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)] #attrs pub struct #name(pub std::string::String); impl #name { pub fn new(s: S) -> Self where Self: std::convert::From, { #name::from(s) } } impl std::convert::From for #name { fn from(s: std::string::String) -> #name { #name(s) } } impl std::convert::From<&str> for #name { fn from(s: &str) -> #name { #name(s.to_string()) } } impl<'a, 'b> query_trails::FromLookAheadValue<#name> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> #name { let s = query_trails::FromLookAheadValue::::from(self); #name(s) } } }; tokens.extend(code); } } #[derive(Debug)] struct Object<'doc> { name: Ident, description: Option<&'doc String>, context_type: &'doc syn::Type, fields: Vec>, implements_interfaces: Vec, } impl<'doc> ToTokens for Object<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Object { name, context_type, description, fields, implements_interfaces, } = self; let mut graphql_attrs = GraphqlAttr::new_object(); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } graphql_attrs.push_key_value(format_ident!("Context"), context_type); graphql_attrs.push_key_value( format_ident!("Scalar"), quote! { juniper_from_schema::juniper::DefaultScalarValue }, ); if !implements_interfaces.is_empty() { graphql_attrs.push_key_value( format_ident!("impl"), quote! { #(#implements_interfaces),* }, ); } let trait_name = fields_trait_name(name); let fields_for_impl = fields .iter() .map(|field| field.to_tokens_for_graphql_object_impl(&trait_name)); let fields_for_trait = fields.iter().map(|field| field.to_tokens_for_trait()); let async_trait_attr = if fields.iter().any(|f| f.directives.r#async.value) { Some(quote! { #[juniper_from_schema::juniper::async_trait] }) } else { None }; let code = quote! { #graphql_attrs impl #name { #(#fields_for_impl)* } #async_trait_attr pub trait #trait_name { #(#fields_for_trait)* } }; tokens.extend(code); } } fn fields_trait_name(name: &Ident) -> Ident { format_ident!("{}Fields", name) } #[derive(Debug)] struct Field<'doc> { description: Option<&'doc String>, name: Ident, error_type: &'doc syn::Type, context_type: &'doc syn::Type, args: Vec>, return_type: Type, directives: FieldDirectives, } impl<'doc> Field<'doc> { fn to_tokens_for_graphql_object_impl<'a>( &'a self, trait_name: &'a Ident, ) -> FieldToTokensGraphqlObject<'a, 'doc> { FieldToTokensGraphqlObject { field: self, trait_name, } } fn to_tokens_for_trait<'a>(&'a self) -> FieldToTokensTrait<'a, 'doc> { FieldToTokensTrait { field: self } } fn to_tokens_for_interface<'a>(&'a self) -> FieldToTokensInterface<'a, 'doc> { FieldToTokensInterface { field: self } } fn to_tokens_for_interface_impl<'a>( &'a self, trait_name: &'a Ident, ) -> FieldToTokensInterfaceImpl<'a, 'doc> { FieldToTokensInterfaceImpl { field: self, trait_name, } } fn to_tokens_for_subscription_impl<'a>( &'a self, trait_name: &'a Ident, ) -> FieldToTokensForSubscriptionImpl<'a, 'doc> { FieldToTokensForSubscriptionImpl { field: self, trait_name, } } fn to_tokens_for_subscription_trait<'a>( &'a self, ) -> FieldToTokensForSubscriptionTrait<'a, 'doc> { FieldToTokensForSubscriptionTrait { field: self } } fn trait_field_name(&self) -> Ident { format_ident!("field_{}", self.name) } fn asyncness(&self) -> Option { if self.directives.r#async.value { Some(syn::token::Async::default()) } else { None } } fn awaitness(&self) -> Option { if self.directives.r#async.value { Some(quote! { .await }) } else { None } } fn return_type_not_wrapped_in_result(&self) -> syn::Type { let return_type = &self.return_type; match &self.directives.ownership { Ownership::Owned => { parse_quote! { #return_type } } Ownership::Borrowed => { parse_quote! { &#return_type } } Ownership::AsRef => { // this case is handled in `graphql_type_to_rust_type` parse_quote! { #return_type } } } } fn full_return_type(&self) -> syn::Type { maybe_wrap_final_return_type_in_result( self.return_type_not_wrapped_in_result(), &self.error_type, &self.directives, ) } fn full_stream_return_type(&self) -> syn::Type { let default_return_type = || { let item_type = self.stream_item_type(); parse_quote! { std::pin::Pin< std::boxed::Box< dyn juniper_from_schema::futures::Stream + std::marker::Send > > } }; if let Some(ty) = &self.directives.stream_type { let ty = syn::parse_str(&ty.value).unwrap_or_else(|_| default_return_type()); maybe_wrap_final_return_type_in_result(ty, &self.error_type, &self.directives) } else { maybe_wrap_final_return_type_in_result( default_return_type(), &self.error_type, &self.directives, ) } } fn stream_item_type(&self) -> syn::Type { if let Some(stream_item_infallible) = &self.directives.stream_item_infallible { if stream_item_infallible.value { self.return_type_not_wrapped_in_result() } else { let ty = self.return_type_not_wrapped_in_result(); let error_type = &self.error_type; parse_quote! { std::result::Result<#ty, #error_type> } } } else { self.return_type_not_wrapped_in_result() } } fn query_trail_type(&self) -> &Type { self.return_type.innermost_type() } fn query_trail_param(&self) -> Option { match self.return_type.kind() { TypeKind::Type => { let query_trail_type = self.query_trail_type(); Some(quote! { trail: &juniper_from_schema::QueryTrail<'r, #query_trail_type, juniper_from_schema::Walked>, }) } TypeKind::Scalar => None, } } } #[derive(Debug)] struct FieldToTokensGraphqlObject<'a, 'doc> { field: &'a Field<'doc>, trait_name: &'a Ident, } #[allow(unused_variables, warnings)] impl<'a, 'doc> ToTokens for FieldToTokensGraphqlObject<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Field { description, name, error_type: _, context_type: _, args, return_type: _, directives, } = self.field; let mut graphql_attrs = GraphqlAttr::new(); if !args.is_empty() { let parts = args.iter().filter_map(|arg| { let name = &arg.name_without_raw_ident; if let Some(description) = &arg.description { Some(quote! { #name(description = #description) }) } else { None } }); graphql_attrs.push_fn(format_ident!("arguments"), parts); }; add_deprecation_graphql_attr_token(directives, &mut graphql_attrs); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); }; let trait_name = self.trait_name; let trait_field_name = self.field.trait_field_name(); let arg_names = args.iter().map(|arg| &arg.name); let return_type = self.field.full_return_type(); let args_for_signature = args .iter() .map(|arg| arg.to_tokens_for_graphql_object_impl()); let rebind_args_with_default_values = args.iter().filter_map(|arg| { if let Some(default_value) = &arg.default_value { let name = &arg.name; Some(quote! { let #name = #name.unwrap_or_else(|| #default_value); }) } else { None } }); let query_trail_arg = if self.field.query_trail_param().is_some() { let query_trail_type = self.field.query_trail_type(); quote! { &juniper_from_schema::QueryTrail::< #query_trail_type, juniper_from_schema::Walked, >::new(&executor.look_ahead()), } } else { quote! {} }; let asyncness = self.field.asyncness(); let awaitness = self.field.awaitness(); tokens.extend(quote! { #graphql_attrs #asyncness fn #name( &self, executor: &Executor, #(#args_for_signature,)* ) -> #return_type { #(#rebind_args_with_default_values)* ::#trait_field_name( self, executor, #query_trail_arg #(#arg_names,)* ) #awaitness } }); } } #[derive(Debug)] struct FieldToTokensTrait<'a, 'doc> { field: &'a Field<'doc>, } impl<'a, 'doc> ToTokens for FieldToTokensTrait<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Field { description: _, name: _, error_type: _, context_type, args, return_type: _, directives: _, } = self.field; let name = self.field.trait_field_name(); let full_return_type = self.field.full_return_type(); let args = args.iter().map(|arg| arg.to_tokens_for_trait()); let query_trail_param = self.field.query_trail_param(); let asyncness = self.field.asyncness(); tokens.extend(quote! { #asyncness fn #name<'s, 'r, 'a>( &'s self, executor: &juniper_from_schema::juniper::Executor<'r, 'a, #context_type>, #query_trail_param #(#args,)* ) -> #full_return_type; }); } } #[derive(Debug)] struct FieldToTokensInterface<'a, 'doc> { field: &'a Field<'doc>, } impl<'a, 'doc> ToTokens for FieldToTokensInterface<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Field { description, name, error_type: _, context_type, args, return_type: _, directives, } = self.field; let return_type = self.field.full_return_type(); let args = args.iter().map(|arg| arg.to_tokens_for_interface()); let mut graphql_attrs = GraphqlAttr::new(); if let Some(desc) = description { graphql_attrs.push_key_value(format_ident!("description"), desc); } add_deprecation_graphql_attr_token(directives, &mut graphql_attrs); let asyncness = self.field.asyncness(); tokens.extend(quote! { #graphql_attrs #asyncness fn #name<'s, 'r, 'a>( &'s self, executor: &juniper_from_schema::juniper::Executor< 'a, 'r, #context_type, juniper_from_schema::juniper::DefaultScalarValue, >, #(#args,)* ) -> #return_type; }) } } #[derive(Debug)] struct FieldToTokensInterfaceImpl<'a, 'doc> { field: &'a Field<'doc>, trait_name: &'a Ident, } impl<'a, 'doc> ToTokens for FieldToTokensInterfaceImpl<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let FieldToTokensInterfaceImpl { field: Field { description: _, name, error_type: _, context_type, args, return_type: _, directives: _, }, trait_name, } = self; // TODO: Remove duplication between this and the object version let trait_field_name = self.field.trait_field_name(); let arg_names = args.iter().map(|arg| &arg.name); let full_return_type = self.field.full_return_type(); // juniper doesn't supporte descriptions on interface field arguments so we cannot add // those let args_for_signature = args .iter() .map(|arg| arg.to_tokens_for_graphql_object_impl()); let rebind_args_with_default_values = args.iter().filter_map(|arg| { if let Some(default_value) = &arg.default_value { let name = &arg.name; Some(quote! { let #name = #name.unwrap_or_else(|| #default_value); }) } else { None } }); let query_trail_arg = if self.field.query_trail_param().is_some() { let query_trail_type = self.field.query_trail_type(); quote! { &juniper_from_schema::QueryTrail::< #query_trail_type, juniper_from_schema::Walked, >::new(&executor.look_ahead()), } } else { quote! {} }; let asyncness = self.field.asyncness(); let awaitness = self.field.awaitness(); let code = quote! { #asyncness fn #name<'s, 'r, 'a>( &'s self, executor: &juniper_from_schema::juniper::Executor< 'a, 'r, #context_type, juniper_from_schema::juniper::DefaultScalarValue, >, #(#args_for_signature),* ) -> #full_return_type { #(#rebind_args_with_default_values)* ::#trait_field_name( self, executor, #query_trail_arg #(#arg_names,)* ) #awaitness } }; tokens.extend(code) } } #[derive(Debug)] struct FieldToTokensForSubscriptionImpl<'a, 'doc> { field: &'a Field<'doc>, trait_name: &'a Ident, } impl<'a, 'doc> ToTokens for FieldToTokensForSubscriptionImpl<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Field { description, name, args, error_type: _, context_type: _, return_type: _, directives: _, } = self.field; let mut graphql_attrs = GraphqlAttr::new(); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); }; if !args.is_empty() { let parts = args.iter().filter_map(|arg| { let name = &arg.name_without_raw_ident; if let Some(description) = &arg.description { Some(quote! { #name(description = #description) }) } else { None } }); graphql_attrs.push_fn(format_ident!("arguments"), parts); }; let trait_name = self.trait_name; let trait_field_name = self.field.trait_field_name(); let arg_names = args.iter().map(|arg| &arg.name); let return_type = self.field.full_stream_return_type(); let args_for_signature = args .iter() .map(|arg| arg.to_tokens_for_graphql_object_impl()); let rebind_args_with_default_values = args.iter().filter_map(|arg| { if let Some(default_value) = &arg.default_value { let name = &arg.name; Some(quote! { let #name = #name.unwrap_or_else(|| #default_value); }) } else { None } }); let query_trail_arg = if self.field.query_trail_param().is_some() { let query_trail_type = self.field.query_trail_type(); quote! { &juniper_from_schema::QueryTrail::< #query_trail_type, juniper_from_schema::Walked, >::new(&executor.look_ahead()), } } else { quote! {} }; let awaitness = self.field.awaitness(); let tryness = if self.field.directives.infallible.value { None } else { Some(quote! { ? }) }; let mut return_result = if self.field.directives.stream_type.is_some() { quote! { resolved_value } } else { quote! { std::boxed::Box::pin(resolved_value) } }; if !self.field.directives.infallible.value { return_result = quote! { Ok(#return_result) }; } let code = quote! { #graphql_attrs async fn #name( executor: &Executor, #(#args_for_signature,)* ) -> #return_type { #(#rebind_args_with_default_values)* let resolved_value = ::#trait_field_name( self, executor, #query_trail_arg #(#arg_names,)* ) #awaitness #tryness; #return_result } }; tokens.extend(code); } } #[derive(Debug)] struct FieldToTokensForSubscriptionTrait<'a, 'doc> { field: &'a Field<'doc>, } impl<'a, 'doc> ToTokens for FieldToTokensForSubscriptionTrait<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Field { context_type, args, description: _, name: _, error_type: _, return_type: _, directives: _, } = self.field; let name = self.field.trait_field_name(); let args = args.iter().map(|arg| arg.to_tokens_for_trait()); let query_trail_param = self.field.query_trail_param(); let asyncness = self.field.asyncness(); let return_type = self.field.full_stream_return_type(); tokens.extend(quote! { #asyncness fn #name<'s, 'r, 'a>( &'s self, executor: &juniper_from_schema::juniper::Executor<'r, 'a, #context_type>, #query_trail_param #(#args,)* ) -> #return_type; }); } } #[derive(Debug)] struct FieldArg<'doc> { name: Ident, name_without_raw_ident: Ident, description: Option<&'doc String>, ty: Type, default_value: Option, } impl<'doc> FieldArg<'doc> { fn to_tokens_for_graphql_object_impl<'a>(&'a self) -> FieldArgToTokensGraphqlObject<'a, 'doc> { FieldArgToTokensGraphqlObject(self) } fn to_tokens_for_trait<'a>(&'a self) -> FieldArgsToTokensTrait<'a, 'doc> { FieldArgsToTokensTrait(self) } fn to_tokens_for_interface<'a>(&'a self) -> FieldArgsToTokensInterface<'a, 'doc> { FieldArgsToTokensInterface(self) } } struct FieldArgToTokensGraphqlObject<'a, 'doc>(&'a FieldArg<'doc>); impl<'a, 'doc> ToTokens for FieldArgToTokensGraphqlObject<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let FieldArg { name, name_without_raw_ident: _, description: _, ty, default_value: _, } = self.0; tokens.extend(quote! { #name: #ty }); } } struct FieldArgsToTokensTrait<'a, 'doc>(&'a FieldArg<'doc>); impl<'a, 'doc> ToTokens for FieldArgsToTokensTrait<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let FieldArg { name, name_without_raw_ident: _, description: _, ty, default_value, } = self.0; let ty = if default_value.is_some() { ty.remove_one_layer_of_nullability() } else { ty }; tokens.extend(quote! { #name: #ty }); } } struct FieldArgsToTokensInterface<'a, 'doc>(&'a FieldArg<'doc>); impl<'a, 'doc> ToTokens for FieldArgsToTokensInterface<'a, 'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let FieldArg { name, description: _, name_without_raw_ident: _, ty, default_value: _, } = self.0; tokens.extend(quote! { #name: #ty }); } } #[derive(Debug)] struct Subscription<'doc> { name: Ident, description: Option<&'doc String>, context_type: &'doc syn::Type, fields: Vec>, } impl<'doc> ToTokens for Subscription<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Subscription { name, description, context_type, fields, } = self; let mut graphql_attrs = GraphqlAttr::new_subscription(); graphql_attrs.push_key_value(format_ident!("Context"), context_type); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } graphql_attrs.push_key_value( format_ident!("Scalar"), quote! { juniper_from_schema::juniper::DefaultScalarValue }, ); let trait_name = fields_trait_name(name); let fields_for_impl = fields .iter() .map(|field| field.to_tokens_for_subscription_impl(&trait_name)); let fields_for_trait = fields .iter() .map(|field| field.to_tokens_for_subscription_trait()); let async_trait_attr = if fields.iter().any(|f| f.directives.r#async.value) { Some(quote! { #[juniper_from_schema::juniper::async_trait] }) } else { None }; tokens.extend(quote! { #graphql_attrs impl #name { #(#fields_for_impl)* } #async_trait_attr pub trait #trait_name { #(#fields_for_trait)* } }); } } #[derive(Debug)] struct Interface<'doc> { description: Option<&'doc String>, name: Ident, trait_name: Ident, fields: Vec>, implementors: Vec, context_type: &'doc syn::Type, } impl<'doc> ToTokens for Interface<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Interface { description, name, trait_name: interface_trait_name, implementors, context_type, fields, } = self; let mut graphql_attrs = GraphqlAttr::new_interface_top_level(); graphql_attrs.push_key_value(format_ident!("for"), quote! { [ #(#implementors),* ] }); graphql_attrs.push_key_value(format_ident!("Context"), quote! { #context_type }); graphql_attrs.push_key_value( format_ident!("Scalar"), quote! { juniper_from_schema::juniper::DefaultScalarValue }, ); graphql_attrs.push_key_value(format_ident!("enum"), name); let name_lit = syn::LitStr::new(&name.to_string(), proc_macro2::Span::call_site()); graphql_attrs.push_key_value(format_ident!("name"), name_lit); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } let fields_for_impl = fields.iter().map(|field| field.to_tokens_for_interface()); tokens.extend(quote! { #graphql_attrs pub trait #interface_trait_name { #(#fields_for_impl)* } }); for implementor in implementors { let trait_name = fields_trait_name(implementor); let fields_for_impl = fields .iter() .map(|field| field.to_tokens_for_interface_impl(&trait_name)); let graphql_attr = GraphqlAttr::new_interface_top_level(); tokens.extend(quote! { #graphql_attr impl #interface_trait_name for #implementor { #(#fields_for_impl)* } }) } } } #[derive(Debug)] struct Union<'doc> { name: Ident, variants: Vec, description: Option<&'doc String>, context_type: &'doc syn::Type, } impl<'doc> ToTokens for Union<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Union { name, variants, description, context_type, } = self; let mut graphql_attrs = GraphqlAttr::new(); graphql_attrs.push_key_value(format_ident!("Context"), context_type); graphql_attrs.push_key_value( format_ident!("Scalar"), quote! { juniper_from_schema::juniper::DefaultScalarValue }, ); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } let from_impls = variants.iter().map(|variant| { let inner_ty = &variant.type_inside; let rust_variant = &variant.rust_name; quote! { impl std::convert::From<#inner_ty> for #name { fn from(inner: #inner_ty) -> #name { #name::#rust_variant(inner) } } } }); tokens.extend(quote! { #[derive(juniper_from_schema::juniper::GraphQLUnion)] #graphql_attrs pub enum #name { #(#variants,)* } #(#from_impls)* }); } } #[derive(Debug)] struct UnionVariant { graphql_name: Ident, rust_name: Ident, type_inside: Box, } impl ToTokens for UnionVariant { fn to_tokens(&self, tokens: &mut TokenStream) { let UnionVariant { graphql_name: _, rust_name, type_inside, } = self; tokens.extend(quote! { #rust_name(#type_inside) }); } } #[derive(Debug)] struct Enum<'doc> { name: Ident, variants: Vec>, description: Option<&'doc String>, } impl<'doc> ToTokens for Enum<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let Enum { name, variants, description, } = self; let graphql_attr = description.map(|description| { quote! { #[graphql(description=#description)] } }); let string_to_enum_value_mappings = variants.iter().map(|variant| { let graphql_name = variant.graphql_name; let variant_name = &variant.name; quote! { &#graphql_name => #name::#variant_name } }); tokens.extend(quote! { #[derive( juniper_from_schema::juniper::GraphQLEnum, Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, )] #graphql_attr pub enum #name { #(#variants),* } impl<'a, 'b> query_trails::FromLookAheadValue<#name> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> #name { match self { juniper_from_schema::juniper::LookAheadValue::Enum(name) => { match name { #(#string_to_enum_value_mappings,)* other => panic!("Invalid enum name: {}", other), } }, juniper_from_schema::juniper::LookAheadValue::Null => panic!( "Failed converting look ahead value. Expected enum type got `null`", ), juniper_from_schema::juniper::LookAheadValue::List(_) => panic!( "Failed converting look ahead value. Expected enum type got `list`", ), juniper_from_schema::juniper::LookAheadValue::Object(_) => panic!( "Failed converting look ahead value. Expected enum type got `object`", ), juniper_from_schema::juniper::LookAheadValue::Scalar(_) => panic!( "Failed converting look ahead value. Expected enum type got `scalar`", ), } } } }); } } #[derive(Debug)] struct EnumVariant<'doc> { name: Ident, deprecation: Deprecation, description: Option<&'doc String>, graphql_name: &'doc str, } impl<'doc> ToTokens for EnumVariant<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let EnumVariant { name, description, deprecation, graphql_name, } = self; let mut graphql_attrs = GraphqlAttr::new(); graphql_attrs.push_key_value(format_ident!("name"), graphql_name); match deprecation { Deprecation::NoDeprecation => {} Deprecation::Deprecated(None) => graphql_attrs.push(format_ident!("deprecated")), Deprecation::Deprecated(Some(reason)) => { graphql_attrs.push_key_value(format_ident!("deprecated"), reason) } }; if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } tokens.extend(quote! { #[allow(missing_docs)] #graphql_attrs #name }) } } #[derive(Debug)] struct InputObject<'doc> { name: Ident, description: Option<&'doc String>, fields: Vec>, } impl<'doc> ToTokens for InputObject<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let InputObject { name, description, fields, } = self; let mut graphql_attrs = GraphqlAttr::new(); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } let field_names = fields .iter() .map(|field| format_ident!("{}_temp", field.name)) .collect::>(); let temp_field_setters = fields .iter() .map(|field| { let name = LitStr::new(&field.name.to_string(), Span::call_site()); let temp_name = format_ident!("{}_temp", field.name); let rust_type = &field.ty; quote! { #name => { #temp_name = Some( query_trails::FromLookAheadValue::<#rust_type>::from( look_ahead_value ) ); }, } }) .collect::>(); let field_setters = fields .iter() .map(|field| { let name = &field.name; let temp_name = format_ident!("{}_temp", &field.name); quote! { #name: #temp_name.unwrap_or_else(|| panic!("Field `{}` was not set", stringify!(#name))), } }) .collect::>(); tokens.extend(quote! { #[derive(juniper_from_schema::juniper::GraphQLInputObject, Clone, Debug)] #graphql_attrs pub struct #name { #(#fields),* } impl<'a, 'b> query_trails::FromLookAheadValue<#name> for &'a juniper_from_schema::juniper::LookAheadValue<'b, juniper_from_schema::juniper::DefaultScalarValue> { fn from(self) -> #name { match self { juniper_from_schema::juniper::LookAheadValue::Object(pairs) => { #( let mut #field_names = None; )* for (look_ahead_key, look_ahead_value) in pairs { match *look_ahead_key { #(#temp_field_setters)* other => panic!("Invalid input object key: {}", other), } } #name { #(#field_setters)* } }, juniper_from_schema::juniper::LookAheadValue::Enum(_) => panic!( "Failed converting look ahead value. Expected object type got `enum`", ), juniper_from_schema::juniper::LookAheadValue::Null => panic!( "Failed converting look ahead value. Expected object type got `null`", ), juniper_from_schema::juniper::LookAheadValue::List(_) => panic!( "Failed converting look ahead value. Expected object type got `list`", ), juniper_from_schema::juniper::LookAheadValue::Scalar(_) => panic!( "Failed converting look ahead value. Expected object type got `scalar`", ), } } } }); } } #[derive(Debug)] struct InputObjectField<'doc> { name: Ident, ty: Type, description: Option<&'doc String>, } impl<'doc> ToTokens for InputObjectField<'doc> { fn to_tokens(&self, tokens: &mut TokenStream) { let InputObjectField { name, ty, description, } = self; let mut graphql_attrs = GraphqlAttr::new(); if let Some(description) = description { graphql_attrs.push_key_value(format_ident!("description"), description); } tokens.extend(quote! { #graphql_attrs pub #name: #ty }) } } #[derive(Debug)] struct SchemaType { query_type: syn::Type, mutation_type: syn::Type, subscription_type: syn::Type, } impl ToTokens for SchemaType { fn to_tokens(&self, tokens: &mut TokenStream) { let SchemaType { query_type, mutation_type, subscription_type, } = self; tokens.extend(quote! { /// The GraphQL schema type generated by `juniper-from-schema`. pub type Schema = juniper_from_schema::juniper::RootNode< 'static, #query_type, #mutation_type, #subscription_type, >; }); } } fn add_deprecation_graphql_attr_token( directives: &FieldDirectives, graphql_attrs: &mut GraphqlAttr, ) { if let Some(Deprecation::Deprecated(reason)) = &directives.deprecated { if let Some(reason) = reason { graphql_attrs.push_key_value(format_ident!("deprecated"), reason); } else { graphql_attrs.push(format_ident!("deprecated")); } } } #[derive(Debug)] enum GraphqlAttr { Normal { items: Vec }, Object { items: Vec }, Interface { items: Vec }, Subscription { items: Vec }, } #[derive(Debug)] enum GraphqlAttrItem { Bare(Ident), KeyValue { key: Ident, value: TokenStream }, Fn { name: Ident, args: Vec }, } impl GraphqlAttr { fn new() -> Self { Self::Normal { items: Vec::new() } } fn new_object() -> Self { Self::Object { items: Vec::new() } } fn new_subscription() -> Self { Self::Subscription { items: Vec::new() } } fn new_interface_top_level() -> Self { Self::Interface { items: Vec::new() } } fn push(&mut self, key: Ident) { let items = match self { GraphqlAttr::Normal { items } => items, GraphqlAttr::Object { items } => items, GraphqlAttr::Interface { items } => items, GraphqlAttr::Subscription { items } => items, }; items.push(GraphqlAttrItem::Bare(key)); } fn push_key_value(&mut self, key: Ident, value: T) { let items = match self { GraphqlAttr::Normal { items } => items, GraphqlAttr::Object { items } => items, GraphqlAttr::Interface { items } => items, GraphqlAttr::Subscription { items } => items, }; items.push(GraphqlAttrItem::KeyValue { key, value: quote! { #value }, }); } fn push_fn(&mut self, name: Ident, values: I) where T: ToTokens, I: Iterator, { let items = match self { GraphqlAttr::Normal { items } => items, GraphqlAttr::Object { items } => items, GraphqlAttr::Interface { items } => items, GraphqlAttr::Subscription { items } => items, }; let args = values .map(|value| { quote! { #value } }) .collect(); items.push(GraphqlAttrItem::Fn { name, args }); } } impl ToTokens for GraphqlAttr { fn to_tokens(&self, tokens: &mut TokenStream) { let (name, items) = match self { GraphqlAttr::Normal { items } => (quote! { graphql }, items), GraphqlAttr::Object { items } => ( quote! { juniper_from_schema::juniper::graphql_object }, items, ), GraphqlAttr::Interface { items } => ( quote! { juniper_from_schema::juniper::graphql_interface }, items, ), GraphqlAttr::Subscription { items } => ( quote! { juniper_from_schema::juniper::graphql_subscription }, items, ), }; let code = quote! { #[ #name ( #(#items),* )] }; tokens.extend(code); } } impl ToTokens for GraphqlAttrItem { fn to_tokens(&self, tokens: &mut TokenStream) { let new_tokens = match self { GraphqlAttrItem::Bare(k) => quote! { #k }, GraphqlAttrItem::KeyValue { key, value } => quote! { #key = #value }, GraphqlAttrItem::Fn { name, args } => quote! { #name ( #(#args),* ) }, }; tokens.extend(new_tokens); } } #[derive(Debug, Copy, Clone)] enum FieldLocation { Object, Interface, Subscription, } fn maybe_wrap_final_return_type_in_result( ty: syn::Type, error_type: &syn::Type, directives: &FieldDirectives, ) -> syn::Type { if directives.infallible.value { ty } else { parse_quote! { std::result::Result<#ty, #error_type> } } } ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/directive_parsing.rs ================================================ use crate::ast_pass::{ code_gen_pass::CodeGenPass, error::{self, ErrorKind, Juniper, UnsupportedDirectiveKind, ValueType}, EmitError, }; use graphql_parser::{query::Value, schema::*}; use std::convert::identity; pub trait FromDirective: Sized { fn from_directive<'doc>(dir: &'doc Directive<'doc, &'doc str>) -> Result; } pub trait FromDirectiveArguments: Sized + Default { const KEY: &'static str; fn from_directive_args<'doc>( args: &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option>; } impl FromDirectiveArguments for Option { const KEY: &'static str = T::KEY; fn from_directive_args<'doc>( args: &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option, ErrorKind>> { match T::from_directive_args(args) { // KEY didn't match None => None, Some(x) => { // KEY *did* match Some(x.map(Some)) } } } } #[derive(Debug)] pub enum Deprecation { NoDeprecation, Deprecated(Option), } impl Default for Deprecation { fn default() -> Self { Self::NoDeprecation } } impl FromDirective for Deprecation { fn from_directive<'doc>(dir: &'doc Directive<'doc, &'doc str>) -> Result { let name = &dir.name; if *name != "deprecated" { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::Deprecation(error::Deprecation::InvalidName( name.to_string(), )), )); } if dir.arguments.len() > 1 { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::Deprecation(error::Deprecation::WrongNumberOfArgs( dir.arguments.len(), )), )); } if let Some((key, value)) = &dir.arguments.first() { if *key != "reason" { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::Deprecation(error::Deprecation::InvalidKey( key.to_string(), )), )); } let reason = match value { Value::String(s) => s.to_string(), other => { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::InvalidType { expected: ValueType::String, actual: ValueType::from(other), }, )); } }; Ok(Deprecation::Deprecated(Some(reason))) } else { Ok(Deprecation::Deprecated(None)) } } } #[derive(Debug)] pub struct JuniperDirective { pub name: String, pub args: T, } impl Default for JuniperDirective { fn default() -> Self { Self { name: "juniper".to_string(), args: T::default(), } } } macro_rules! impl_from_directive_for { { ( $( $name:ident ),* ) } => { #[allow(unused_parens)] impl<$($name),*> FromDirective for JuniperDirective<($($name),*)> where $($name: FromDirectiveArguments,)* { #[allow(non_snake_case)] fn from_directive<'doc>(dir: &'doc Directive<'doc, &'doc str>) -> Result { let name = &dir.name; if *name != "juniper" { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::Juniper(Juniper::InvalidName(name.to_string())), )); } $(let mut $name = None::<$name>;)* let mut args: Vec)>> = dir.arguments.iter().map(Some).collect(); for idx in 0..args.len() { let arg = &args[idx].unwrap(); $( if let Some(arg) = $name::from_directive_args(arg) { $name.replace(arg?); args[idx] = None; continue; } )* } let unknown_args = args.into_iter().filter_map(identity).collect::>(); if !unknown_args.is_empty() { let arg_names = unknown_args .iter() .map(|(name, _)| name.to_string()) .collect::>(); return Err(ErrorKind::UnknownDirective { suggestions: arg_names }); } $(let $name = $name.unwrap_or_else($name::default);)* Ok(Self { name: name.to_string(), args: ($($name),*), }) } } }; } impl_from_directive_for! { (T) } impl_from_directive_for! { (T1, T2) } impl_from_directive_for! { (T1, T2, T3) } impl_from_directive_for! { (T1, T2, T3, T4) } impl_from_directive_for! { (T1, T2, T3, T4, T5) } #[derive(Debug)] pub struct FieldDirectives { pub ownership: Ownership, pub deprecated: Option, pub infallible: Infallible, pub r#async: Async, pub stream_type: Option, pub stream_item_infallible: Option, } #[derive(Debug, Eq, PartialEq)] pub enum Ownership { Owned, Borrowed, AsRef, } impl Ownership { pub fn is_as_ref(&self) -> bool { matches!(self, Ownership::AsRef) } } impl Default for Ownership { fn default() -> Self { Self::Borrowed } } impl FromDirectiveArguments for Ownership { const KEY: &'static str = "ownership"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let ownership_raw = value_as_string(value)?; let ownership = match ownership_raw { "owned" => Ownership::Owned, "borrowed" => Ownership::Borrowed, "as_ref" => Ownership::AsRef, value => { return Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::Ownership(error::Ownership::InvalidValue( value.to_string(), )), )); } }; Ok(ownership) })(); Some(directive) } } #[derive(Debug)] pub struct Infallible { pub value: bool, } impl Default for Infallible { fn default() -> Self { Infallible { value: false } } } impl FromDirectiveArguments for Infallible { const KEY: &'static str = "infallible"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let value = value_as_bool(value)?; Ok(Self { value }) })(); Some(directive) } } #[derive(Debug)] pub struct Async { pub value: bool, } impl FromDirectiveArguments for Async { const KEY: &'static str = "async"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let value = value_as_bool(value)?; Ok(Self { value }) })(); Some(directive) } } impl Default for Async { fn default() -> Self { Async { value: false } } } #[derive(Debug, Default)] pub struct StreamType { pub value: String, } impl FromDirectiveArguments for StreamType { const KEY: &'static str = "stream_type"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let value = value_as_string(value)?; Ok(Self { value: value.to_string(), }) })(); Some(directive) } } #[derive(Debug, Default)] pub struct StreamItemInfallible { pub value: bool, } impl FromDirectiveArguments for StreamItemInfallible { const KEY: &'static str = "stream_item_infallible"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let value = value_as_bool(value)?; Ok(Self { value }) })(); Some(directive) } } #[derive(Debug)] pub struct DateTimeScalarArguments { pub with_time_zone: bool, } impl Default for DateTimeScalarArguments { fn default() -> Self { DateTimeScalarArguments { with_time_zone: true, } } } impl FromDirectiveArguments for DateTimeScalarArguments { const KEY: &'static str = "with_time_zone"; fn from_directive_args<'doc>( (key, value): &'doc (&'doc str, Value<'doc, &'doc str>), ) -> Option> { if *key != Self::KEY { return None; } let directive = (|| { let with_time_zone = value_as_bool(value)?; Ok(Self { with_time_zone }) })(); Some(directive) } } fn value_as_string<'doc>(value: &'doc Value<'doc, &'doc str>) -> Result<&'doc str, ErrorKind> { match value { Value::String(x) => Ok(x), other => Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::InvalidType { expected: ValueType::String, actual: ValueType::from(other), }, )), } } fn value_as_bool<'doc>(value: &'doc Value<'doc, &'doc str>) -> Result { match value { Value::Boolean(x) => Ok(*x), other => Err(ErrorKind::UnsupportedDirective( UnsupportedDirectiveKind::InvalidType { expected: ValueType::Boolean, actual: ValueType::from(other), }, )), } } pub trait ParseDirective { type Output; fn parse_directives(&mut self, input: T) -> Self::Output; } impl<'doc> ParseDirective<&'doc Field<'doc, &'doc str>> for CodeGenPass<'doc> { type Output = FieldDirectives; fn parse_directives(&mut self, input: &'doc Field<'doc, &'doc str>) -> Self::Output { let mut ownership = Ownership::default(); let mut deprecated = None::; let mut infallible = Infallible::default(); let mut r#async = Async::default(); let mut stream_type = None::; let mut stream_item_infallible = None::; for dir in &input.directives { if let Ok(juniper_directive) = JuniperDirective::<( Ownership, Infallible, Async, Option, Option, )>::from_directive(dir) { ownership = juniper_directive.args.0; infallible = juniper_directive.args.1; r#async = juniper_directive.args.2; stream_type = juniper_directive.args.3; stream_item_infallible = juniper_directive.args.4; continue; } if let Ok(x) = Deprecation::from_directive(dir) { deprecated = Some(x); continue; } self.emit_error( dir.position, ErrorKind::UnknownDirective { suggestions: vec![], }, ); } FieldDirectives { ownership, deprecated, infallible, r#async, stream_type, stream_item_infallible, } } } impl<'doc> ParseDirective<&'doc EnumValue<'doc, &'doc str>> for CodeGenPass<'doc> { type Output = Deprecation; fn parse_directives(&mut self, input: &'doc EnumValue<'doc, &'doc str>) -> Self::Output { let mut deprecated = Deprecation::default(); for dir in &input.directives { match Deprecation::from_directive(dir) { Ok(x) => { deprecated = x; } Err(err) => { self.emit_error(dir.position, err); } } } deprecated } } #[derive(Debug)] pub struct DateTimeScalarType<'a>(pub &'a ScalarType<'a, &'a str>); impl<'doc, T> ParseDirective> for T where T: EmitError, { type Output = DateTimeScalarArguments; fn parse_directives(&mut self, input: DateTimeScalarType<'doc>) -> Self::Output { let mut args = DateTimeScalarArguments::default(); for dir in &input.0.directives { match JuniperDirective::::from_directive(dir) { Ok(x) => { args = x.args; } Err(err) => { self.emit_error(dir.position, err); } } } args } } macro_rules! supports_no_directives { ($ty:ty) => { impl<'doc> ParseDirective<&'doc $ty> for CodeGenPass<'doc> { type Output = (); fn parse_directives(&mut self, input: &'doc $ty) -> Self::Output { for directive in &input.directives { self.emit_error( directive.position, ErrorKind::UnknownDirective { suggestions: vec![], }, ); } } } }; } supports_no_directives!(SchemaDefinition<'doc, &'doc str>); supports_no_directives!(ScalarType<'doc, &'doc str>); supports_no_directives!(ObjectType<'doc, &'doc str>); supports_no_directives!(InterfaceType<'doc, &'doc str>); supports_no_directives!(UnionType<'doc, &'doc str>); supports_no_directives!(EnumType<'doc, &'doc str>); supports_no_directives!(InputObjectType<'doc, &'doc str>); supports_no_directives!(InputValue<'doc, &'doc str>); ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/error.rs ================================================ use colored::*; use graphql_parser::{query::Value, Pos}; use std::fmt::{self, Write}; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct Error { pub(super) pos: Pos, pub(super) kind: ErrorKind, } impl Error { pub fn display<'a>(&'a self, raw_schema: &'a str) -> ErrorDisplay<'a> { ErrorDisplay { error: self, raw_schema, } } } #[derive(Debug)] pub struct ErrorDisplay<'a> { error: &'a Error, raw_schema: &'a str, } impl<'a> fmt::Display for ErrorDisplay<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let schema_lines = self.raw_schema.lines().collect::>(); let number_of_digits_in_line_count = number_of_digits(self.error.pos.line as i32); let indent = 4; writeln!( f, "{error}: {kind}", error = "error".bright_red(), kind = self.error.kind.description() )?; writeln!( f, "{indent} --> schema:{line}:{col}", indent = "".indent(number_of_digits_in_line_count - 1), line = self.error.pos.line, col = self.error.pos.column )?; writeln!(f, "{} |", "".indent(number_of_digits_in_line_count))?; writeln!( f, "{} |{}", self.error.pos.line, schema_lines[self.error.pos.line - 1].indent(indent), )?; writeln!( f, "{} |{}{}", "".indent(number_of_digits_in_line_count), "".indent(self.error.pos.column - 1 + indent), "^".bright_red(), )?; if let Some(notes) = self.error.kind.notes() { writeln!(f)?; for line in notes.lines() { writeln!(f, "{}", line)?; } } Ok(()) } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Deprecation { InvalidName(String), WrongNumberOfArgs(usize), InvalidKey(String), } impl fmt::Display for Deprecation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Deprecation::*; match self { InvalidName(name) => write!(f, "Invalid name. Expected `deprecated`, got `{}`", name), WrongNumberOfArgs(count) => { write!(f, "Wrong number of args. Expected 0 or 1, got `{}`", count) } InvalidKey(key) => write!(f, "Invalid key. Expected `reason`, got `{}`", key), } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum ValueType { Variable, Int, Float, String, Boolean, Null, Enum, List, Object, } impl<'doc> From<&'doc Value<'doc, &'doc str>> for ValueType { fn from(value: &'doc Value<'doc, &'doc str>) -> Self { match value { Value::String(_) => ValueType::String, Value::Variable(_) => ValueType::Variable, Value::Int(_) => ValueType::Int, Value::Float(_) => ValueType::Float, Value::Boolean(_) => ValueType::Boolean, Value::Null => ValueType::Null, Value::Enum(_) => ValueType::Enum, Value::List(_) => ValueType::List, Value::Object(_) => ValueType::Object, } } } impl fmt::Display for ValueType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use ValueType::*; match self { String => write!(f, "String"), Variable => write!(f, "Variable"), Int => write!(f, "Int"), Float => write!(f, "Float"), Boolean => write!(f, "Boolean"), Null => write!(f, "Null"), Enum => write!(f, "Enum"), List => write!(f, "List"), Object => write!(f, "Object"), } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Ownership { InvalidValue(String), } impl fmt::Display for Ownership { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidValue(name) => write!( f, "Invalid value. Expected `owned`, `borrowed`, or `as_ref`, got `{}`", name ), } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Juniper { InvalidName(String), } impl fmt::Display for Juniper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidName(name) => write!(f, "Invalid name `{}`. Expected `juniper`", name), } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum UnsupportedDirectiveKind { Deprecation(Deprecation), Ownership(Ownership), Juniper(Juniper), InvalidType { actual: ValueType, expected: ValueType, }, } impl fmt::Display for UnsupportedDirectiveKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Deprecation(inner) => write!(f, "{}", inner), Self::Ownership(inner) => write!(f, "{}", inner), Self::Juniper(inner) => write!(f, "{}", inner), Self::InvalidType { expected, actual } => { write!(f, "Invalid type. Expected `{}`, got `{}`", expected, actual) } } } } #[allow(dead_code)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum ErrorKind { DateTimeScalarNotDefined, DateScalarNotDefined, UuidScalarNotDefined, UrlScalarNotDefined, SpecialCaseScalarWithDescription, UnsupportedDirective(UnsupportedDirectiveKind), UnknownDirective { suggestions: Vec, }, NoQueryType, NonnullableFieldWithDefaultValue, TypeExtensionNotSupported, UnionFieldTypeMismatch { union_name: String, field_name: String, type_a: String, field_type_a: String, type_b: String, field_type_b: String, }, VariableDefaultValue, InputTypeFieldWithDefaultValue, AsRefOwnershipForNamedType, FieldNameInSnakeCase, UppercaseUuidScalar, InvalidJuniperDirective(String, Option), CannotDeclareBuiltinAsScalar, InvalidStreamReturnType(String), StreamTypeNotSupportedHere, StreamItemInfallibleNotSupportedHere, SubscriptionsCannotImplementInterfaces, SubscriptionFieldMustBeOwned, } impl ErrorKind { fn description(&self) -> String { match self { ErrorKind::DateTimeScalarNotDefined => { "You have to define a custom scalar called `DateTimeUtc` to use this type".to_string() } ErrorKind::DateScalarNotDefined => { "You have to define a custom scalar called `Date` to use this type".to_string() } ErrorKind::UuidScalarNotDefined => { "You have to define a custom scalar called `Uuid` to use this type".to_string() } ErrorKind::UrlScalarNotDefined => { "You have to define a custom scalar called `Url` to use this type".to_string() } ErrorKind::SpecialCaseScalarWithDescription => { "Special case scalars don't support having descriptions because the Rust types are defined in external crates".to_string() } ErrorKind::UnsupportedDirective(_) => { "Unsupported directive.".to_string() } ErrorKind::UnknownDirective { suggestions: _ } => { "Unknown directive".to_string() } ErrorKind::NoQueryType => "Schema doesn't have root a Query type".to_string(), ErrorKind::NonnullableFieldWithDefaultValue => { "Fields with default arguments values must be nullable".to_string() } ErrorKind::VariableDefaultValue => { "Default arguments cannot refer to variables".to_string() } ErrorKind::TypeExtensionNotSupported => "Type extentions are not supported".to_string(), ErrorKind::UnionFieldTypeMismatch { union_name, .. } => format!( "Error while generating `QueryTrail` for union `{}`", union_name ), ErrorKind::InputTypeFieldWithDefaultValue => { "Default values for input type fields are not supported".to_string() } ErrorKind::AsRefOwnershipForNamedType => { "@juniper(ownership: \"as_ref\") is only supported on `Option` and `Vec` types" .to_string() } ErrorKind::FieldNameInSnakeCase => { "Field names must be camelCase, not snake_case".to_string() } ErrorKind::UppercaseUuidScalar => { "The UUID must be named `Uuid`".to_string() } ErrorKind::InvalidJuniperDirective(msg, _) => { msg.clone() } ErrorKind::CannotDeclareBuiltinAsScalar => { "You cannot declare scalars with names matching a built-in".to_string() } ErrorKind::InvalidStreamReturnType(_) => { "Invalid stream return type. This doesn't seem to be a valid Rust type".to_string() } ErrorKind::StreamTypeNotSupportedHere => { "`stream_type` directive argument is only supported on subscription fields".to_string() } ErrorKind::StreamItemInfallibleNotSupportedHere => { "`stream_item_infallible` directive argument is only supported on subscription fields".to_string() } ErrorKind::SubscriptionsCannotImplementInterfaces => { "Subscriptions cannot implement interfaces".to_string() } ErrorKind::SubscriptionFieldMustBeOwned => { "Subscription fields must use `@juniper(ownership: \"owned\")`".to_string() } } } #[allow(unused_must_use)] fn notes(&self) -> Option { match self { ErrorKind::UnionFieldTypeMismatch { union_name, field_name, type_a, type_b, field_type_a, field_type_b, } => { let mut f = String::new(); writeln!( f, "`{}.{}` and `{}.{}` are not the same type", type_a, field_name, type_b, field_name ); writeln!( f, " `{}.{}` is of type `{}`", type_a, field_name, field_type_a ); writeln!( f, " `{}.{}` is of type `{}`", type_b, field_name, field_type_b ); writeln!(f, "That makes it impossible to generate code for the method `QueryTrail<_, {}, _>::{}()`", union_name, field_name); writeln!( f, "It would have to return `{}` if `{}` is `{},` but `{}` if it is a `{}`", field_type_a, union_name, type_a, field_type_b, type_b ); Some(f) } ErrorKind::DateTimeScalarNotDefined => { Some("Insert `scalar DateTimeUtc` into your schema".to_string()) } ErrorKind::DateScalarNotDefined => { Some("Insert `scalar Date` into your schema".to_string()) } ErrorKind::InputTypeFieldWithDefaultValue => { let mut f = String::new(); writeln!(f, "Consider using default field arguments instead"); writeln!(f); writeln!(f, "It is not supported because the spec isn't clear"); writeln!(f, "about what should happen when there are defaults"); writeln!(f, "in both the input type definition and field argument"); writeln!(f); writeln!( f, "See https://github.com/webonyx/graphql-php/issues/350 for an example" ); Some(f) } ErrorKind::FieldNameInSnakeCase => Some( "This is because Juniper always converts all field names to camelCase".to_string(), ), ErrorKind::UnsupportedDirective(reason) => Some(format!("{}", reason)), ErrorKind::UnknownDirective { suggestions } => { if suggestions.is_empty() { None } else { Some(format!("Did you mean: {}?", suggestions.join(", "))) } } ErrorKind::UppercaseUuidScalar => { Some("This is to be consistent with the naming the \"uuid\" crate".to_string()) } ErrorKind::InvalidJuniperDirective(_, notes) => notes.to_owned(), ErrorKind::InvalidStreamReturnType(syn_error) => Some(syn_error.to_owned()), _ => None, } } } trait Indent { fn indent(&self, size: usize) -> String; } impl Indent for &str { fn indent(&self, size: usize) -> String { if size == 0 { return (*self).to_string(); } let mut out = String::new(); for _ in 0..size { out.push(' '); } out.push_str(self); out } } fn number_of_digits(n: i32) -> usize { if n == 0 { return 1; } let n = f64::from(n); f64::floor(f64::log10(n)) as usize + 1 } #[cfg(test)] mod test { use super::*; #[test] fn test_number_of_digits() { assert_eq!(1, number_of_digits(0)); assert_eq!(1, number_of_digits(1)); assert_eq!(1, number_of_digits(4)); assert_eq!(2, number_of_digits(10)); assert_eq!(7, number_of_digits(1_000_000)); } } ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/mod.rs ================================================ pub mod code_gen_pass; pub mod directive_parsing; pub mod error; pub mod schema_visitor; pub mod validations; use self::{ directive_parsing::{DateTimeScalarType, ParseDirective}, error::{Error, ErrorKind}, schema_visitor::{visit_document, SchemaVisitor}, }; use graphql_parser::{schema::*, Pos}; use std::collections::{BTreeSet, HashMap, HashSet}; pub fn type_name<'doc>(type_: &Type<'doc, &'doc str>) -> &'doc str { match &*type_ { Type::NamedType(name) => &name, Type::ListType(item_type) => type_name(&*item_type), Type::NonNullType(item_type) => type_name(&*item_type), } } #[derive(Debug, Clone, Copy)] pub enum TypeKind { Scalar, Type, } pub trait EmitError { fn emit_error(&mut self, pos: Pos, kind: ErrorKind); } impl EmitError for BTreeSet { fn emit_error(&mut self, pos: Pos, kind: ErrorKind) { let error = Error { pos, kind }; self.insert(error); } } #[derive(Debug)] pub struct AstData<'doc> { interface_implementors: HashMap<&'doc str, Vec<&'doc str>>, user_scalars: HashSet<&'doc str>, enum_types: HashSet<&'doc str>, union_types: HashSet<&'doc str>, input_object_field_types: HashMap<&'doc str, HashMap<&'doc str, &'doc Type<'doc, &'doc str>>>, errors: BTreeSet, include_time_zone_on_date_time_scalar: bool, subscription_type_name: Option<&'doc str>, } impl<'doc> SchemaVisitor<'doc> for AstData<'doc> { fn visit_object_type(&mut self, obj: &'doc ObjectType<'doc, &'doc str>) { for interface in &obj.implements_interfaces { self.interface_implementors .entry(interface) .or_insert_with(Vec::new) .push(&obj.name); } } fn visit_scalar_type(&mut self, scalar: &'doc ScalarType<'doc, &'doc str>) { match &*scalar.name { name if name == crate::DATE_TIME_SCALAR_NAME => { let args = self.parse_directives(DateTimeScalarType(scalar)); if args.with_time_zone { self.include_time_zone_on_date_time_scalar = true; } else { self.include_time_zone_on_date_time_scalar = false; } self.user_scalars.insert(name); } name => { self.user_scalars.insert(name); } }; } fn visit_enum_type(&mut self, enum_type: &'doc EnumType<'doc, &'doc str>) { self.enum_types.insert(&enum_type.name); } fn visit_union_type(&mut self, union_type: &'doc UnionType<'doc, &'doc str>) { self.union_types.insert(&union_type.name); } fn visit_input_object_type(&mut self, input_type: &'doc InputObjectType<'doc, &'doc str>) { for field in &input_type.fields { self.input_object_field_types .entry(&input_type.name) .or_insert_with(HashMap::new) .insert(&field.name, &field.value_type); } } fn visit_schema_definition(&mut self, node: &'doc SchemaDefinition<'doc, &'doc str>) { if let Some(subscription_type_name) = node.subscription { self.subscription_type_name = Some(subscription_type_name); } } } impl<'doc> AstData<'doc> { pub fn new_from_doc(doc: &'doc Document<'doc, &'doc str>) -> Result> { let mut data = Self::new(); visit_document(&mut data, doc); if data.errors.is_empty() { Ok(data) } else { Err(data.errors) } } fn new() -> Self { Self { interface_implementors: Default::default(), user_scalars: Default::default(), enum_types: Default::default(), union_types: Default::default(), input_object_field_types: Default::default(), errors: Default::default(), include_time_zone_on_date_time_scalar: true, subscription_type_name: None, } } pub fn get_implementors_of_interface(&self, name: &str) -> Option<&Vec<&'doc str>> { self.interface_implementors.get(name) } pub fn date_scalar_defined(&self) -> bool { self.is_scalar(crate::DATE_SCALAR_NAME) } pub fn date_time_scalar_defined(&self) -> bool { self.is_scalar(crate::DATE_TIME_SCALAR_NAME) } pub fn date_time_scalar_definition(&self) -> Option { if self.is_scalar(crate::DATE_TIME_SCALAR_NAME) { if self.include_time_zone_on_date_time_scalar { Some(DateTimeScalarDefinition::WithTimeZone) } else { Some(DateTimeScalarDefinition::WithoutTimeZone) } } else { None } } pub fn uuid_scalar_defined(&self) -> bool { self.is_scalar(crate::UUID_SCALAR_NAME) } pub fn url_scalar_defined(&self) -> bool { self.is_scalar(crate::URL_SCALAR_NAME) } pub fn is_scalar(&self, name: &str) -> bool { self.user_scalars.contains(name) } pub fn is_enum_type(&self, name: &str) -> bool { self.enum_types.contains(name) } pub fn is_union_type(&self, name: &str) -> bool { self.union_types.contains(name) } pub fn is_interface_type(&self, name: &str) -> bool { self.interface_implementors.contains_key(name) } #[allow(clippy::ptr_arg)] pub fn input_object_field_is_nullable( &self, input_type_name: &'doc str, field_name: &'doc str, ) -> Option { use graphql_parser::query::Type::*; let field_map = self.input_object_field_types.get(input_type_name)?; let type_ = field_map.get(field_name)?; match type_ { NamedType(_) => Some(true), ListType(_) => Some(true), NonNullType(_) => Some(false), } } pub fn input_object_field_names( &self, input_type_name: &'doc str, ) -> Option> { let field_map = self.input_object_field_types.get(input_type_name)?; let mut out = HashSet::new(); for key in field_map.keys() { out.insert(*key); } Some(out) } pub fn input_object_field_type_name( &self, input_type_name: &'doc str, field_name: &'doc str, ) -> Option<&'doc str> { let field_map = &self.input_object_field_types.get(input_type_name)?; let type_ = field_map.get(field_name)?; Some(type_name(&type_)) } pub fn is_subscription_type(&self, name: &'doc str) -> bool { self.subscription_type_name .map(|s| s == name) .unwrap_or(false) } } impl<'doc> EmitError for AstData<'doc> { fn emit_error(&mut self, pos: Pos, kind: ErrorKind) { let error = Error { pos, kind }; self.errors.insert(error); } } pub enum DateTimeScalarDefinition { WithTimeZone, WithoutTimeZone, } #[derive(Eq, PartialEq, Debug)] pub enum NullableType<'a> { NamedType(&'a str), ListType(Box>), NullableType(Box>), } impl<'a> NullableType<'a> { pub fn from_schema_type(ty: &Type<'a, &'a str>) -> Self { map(&ty) } } #[cfg(test)] impl<'a> NullableType<'a> { fn debug_print(&self) -> String { match self { NullableType::NamedType(name) => (*name).to_string(), NullableType::ListType(inner) => format!("List({})", inner.debug_print()), NullableType::NullableType(inner) => format!("Nullable({})", inner.debug_print()), } } } fn map<'a>(ty: &Type<'a, &'a str>) -> NullableType<'a> { match ty { inner @ Type::NamedType(_) => map_inner(inner, false), Type::ListType(item_type) => { let item_type = map_inner(&*item_type, false); let list = NullableType::ListType(Box::new(item_type)); NullableType::NullableType(Box::new(list)) } Type::NonNullType(inner) => map_inner(&*inner, true), } } fn map_inner<'a>(ty: &Type<'a, &'a str>, inside_non_null: bool) -> NullableType<'a> { match ty { Type::NamedType(name) => { let inner_mapped = NullableType::NamedType(&name); if inside_non_null { inner_mapped } else { NullableType::NullableType(Box::new(inner_mapped)) } } Type::ListType(inner) => { let inner_mapped = NullableType::ListType(Box::new(map(&*inner))); if inside_non_null { inner_mapped } else { NullableType::NullableType(Box::new(inner_mapped)) } } Type::NonNullType(inner) => map_inner(&*inner, true), } } #[cfg(test)] mod test { use super::*; #[test] fn named_type() { let input = Type::NonNullType(Box::new(Type::NamedType("Int"))); let expected = "Int".to_string(); assert_eq!(map(&input).debug_print(), expected); let input = Type::NamedType("Int"); let expected = "Nullable(Int)".to_string(); assert_eq!(map(&input).debug_print(), expected); let input = Type::NonNullType(Box::new(Type::ListType(Box::new(Type::NonNullType( Box::new(Type::NamedType("Int")), ))))); let expected = "List(Int)".to_string(); assert_eq!(map(&input).debug_print(), expected); let input = Type::ListType(Box::new(Type::NonNullType(Box::new(Type::NamedType( "Int", ))))); let expected = "Nullable(List(Int))".to_string(); assert_eq!(map(&input).debug_print(), expected); let input = Type::NonNullType(Box::new(Type::ListType(Box::new(Type::NamedType("Int"))))); let expected = "List(Nullable(Int))".to_string(); assert_eq!(map(&input).debug_print(), expected); let input = Type::ListType(Box::new(Type::NamedType("Int"))); let expected = "Nullable(List(Nullable(Int)))".to_string(); assert_eq!(map(&input).debug_print(), expected); } } ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/schema_visitor.rs ================================================ #![deny(unused_variables)] use graphql_parser::{ schema, schema::{Definition, TypeDefinition, TypeExtension}, }; pub trait SchemaVisitor<'doc> { #[inline] fn visit_document(&mut self, _: &'doc schema::Document<'doc, &'doc str>) {} #[inline] fn visit_schema_definition(&mut self, _: &'doc schema::SchemaDefinition<'doc, &'doc str>) {} #[inline] fn visit_directive_definition( &mut self, _: &'doc schema::DirectiveDefinition<'doc, &'doc str>, ) { } #[inline] fn visit_type_definition(&mut self, _: &'doc schema::TypeDefinition<'doc, &'doc str>) {} #[inline] fn visit_scalar_type(&mut self, _: &'doc schema::ScalarType<'doc, &'doc str>) {} #[inline] fn visit_object_type(&mut self, _: &'doc schema::ObjectType<'doc, &'doc str>) {} #[inline] fn visit_interface_type(&mut self, _: &'doc schema::InterfaceType<'doc, &'doc str>) {} #[inline] fn visit_union_type(&mut self, _: &'doc schema::UnionType<'doc, &'doc str>) {} #[inline] fn visit_enum_type(&mut self, _: &'doc schema::EnumType<'doc, &'doc str>) {} #[inline] fn visit_input_object_type(&mut self, _: &'doc schema::InputObjectType<'doc, &'doc str>) {} #[inline] fn visit_type_extension(&mut self, _: &'doc schema::TypeExtension<'doc, &'doc str>) {} #[inline] fn visit_scalar_type_extension( &mut self, _: &'doc schema::ScalarTypeExtension<'doc, &'doc str>, ) { } #[inline] fn visit_object_type_extension( &mut self, _: &'doc schema::ObjectTypeExtension<'doc, &'doc str>, ) { } #[inline] fn visit_interface_type_extension( &mut self, _: &'doc schema::InterfaceTypeExtension<'doc, &'doc str>, ) { } #[inline] fn visit_union_type_extension(&mut self, _: &'doc schema::UnionTypeExtension<'doc, &'doc str>) { } #[inline] fn visit_enum_type_extension(&mut self, _: &'doc schema::EnumTypeExtension<'doc, &'doc str>) {} #[inline] fn visit_input_object_type_extension( &mut self, _: &'doc schema::InputObjectTypeExtension<'doc, &'doc str>, ) { } #[inline] fn and(self, rhs: T) -> And where Self: Sized, { And { lhs: self, rhs } } } pub fn visit_document<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::Document<'doc, &'doc str>, ) { v.visit_document(node); for def in &node.definitions { match def { Definition::SchemaDefinition(inner) => visit_schema_definition(v, inner), Definition::TypeDefinition(inner) => visit_type_definition(v, inner), Definition::TypeExtension(inner) => visit_type_extension(v, inner), Definition::DirectiveDefinition(inner) => visit_directive_definition(v, inner), } } } pub fn visit_schema_definition<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::SchemaDefinition<'doc, &'doc str>, ) { v.visit_schema_definition(node) } pub fn visit_directive_definition<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::DirectiveDefinition<'doc, &'doc str>, ) { v.visit_directive_definition(node) } pub fn visit_type_definition<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::TypeDefinition<'doc, &'doc str>, ) { v.visit_type_definition(node); match node { TypeDefinition::Scalar(inner) => visit_scalar_type(v, inner), TypeDefinition::Object(inner) => visit_object_type(v, inner), TypeDefinition::Interface(inner) => visit_interface_type(v, inner), TypeDefinition::Union(inner) => visit_union_type(v, inner), TypeDefinition::Enum(inner) => visit_enum_type(v, inner), TypeDefinition::InputObject(inner) => visit_input_object_type(v, inner), } } pub fn visit_scalar_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::ScalarType<'doc, &'doc str>, ) { v.visit_scalar_type(node) } pub fn visit_object_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::ObjectType<'doc, &'doc str>, ) { v.visit_object_type(node) } pub fn visit_interface_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::InterfaceType<'doc, &'doc str>, ) { v.visit_interface_type(node) } pub fn visit_union_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::UnionType<'doc, &'doc str>, ) { v.visit_union_type(node) } pub fn visit_enum_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::EnumType<'doc, &'doc str>, ) { v.visit_enum_type(node) } pub fn visit_input_object_type<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::InputObjectType<'doc, &'doc str>, ) { v.visit_input_object_type(node) } pub fn visit_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::TypeExtension<'doc, &'doc str>, ) { v.visit_type_extension(node); match node { TypeExtension::Scalar(inner) => visit_scalar_type_extension(v, inner), TypeExtension::Object(inner) => visit_object_type_extension(v, inner), TypeExtension::Interface(inner) => visit_interface_type_extension(v, inner), TypeExtension::Union(inner) => visit_union_type_extension(v, inner), TypeExtension::Enum(inner) => visit_enum_type_extension(v, inner), TypeExtension::InputObject(inner) => visit_input_object_type_extension(v, inner), } } pub fn visit_scalar_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::ScalarTypeExtension<'doc, &'doc str>, ) { v.visit_scalar_type_extension(node) } pub fn visit_object_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::ObjectTypeExtension<'doc, &'doc str>, ) { v.visit_object_type_extension(node) } pub fn visit_interface_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::InterfaceTypeExtension<'doc, &'doc str>, ) { v.visit_interface_type_extension(node) } pub fn visit_union_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::UnionTypeExtension<'doc, &'doc str>, ) { v.visit_union_type_extension(node) } pub fn visit_enum_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::EnumTypeExtension<'doc, &'doc str>, ) { v.visit_enum_type_extension(node) } pub fn visit_input_object_type_extension<'doc, V: SchemaVisitor<'doc>>( v: &mut V, node: &'doc schema::InputObjectTypeExtension<'doc, &'doc str>, ) { v.visit_input_object_type_extension(node) } #[derive(Debug)] pub struct And { lhs: A, rhs: B, } impl And { pub fn into_inner(self) -> (A, B) { (self.lhs, self.rhs) } } impl<'doc, A, B> SchemaVisitor<'doc> for And where A: SchemaVisitor<'doc>, B: SchemaVisitor<'doc>, { #[inline] fn visit_document(&mut self, node: &'doc schema::Document<'doc, &'doc str>) { self.lhs.visit_document(node); self.rhs.visit_document(node); } #[inline] fn visit_schema_definition(&mut self, node: &'doc schema::SchemaDefinition<'doc, &'doc str>) { self.lhs.visit_schema_definition(node); self.rhs.visit_schema_definition(node); } #[inline] fn visit_directive_definition( &mut self, node: &'doc schema::DirectiveDefinition<'doc, &'doc str>, ) { self.lhs.visit_directive_definition(node); self.rhs.visit_directive_definition(node); } #[inline] fn visit_type_definition(&mut self, node: &'doc schema::TypeDefinition<'doc, &'doc str>) { self.lhs.visit_type_definition(node); self.rhs.visit_type_definition(node); } #[inline] fn visit_scalar_type(&mut self, node: &'doc schema::ScalarType<'doc, &'doc str>) { self.lhs.visit_scalar_type(node); self.rhs.visit_scalar_type(node); } #[inline] fn visit_object_type(&mut self, node: &'doc schema::ObjectType<'doc, &'doc str>) { self.lhs.visit_object_type(node); self.rhs.visit_object_type(node); } #[inline] fn visit_interface_type(&mut self, node: &'doc schema::InterfaceType<'doc, &'doc str>) { self.lhs.visit_interface_type(node); self.rhs.visit_interface_type(node); } #[inline] fn visit_union_type(&mut self, node: &'doc schema::UnionType<'doc, &'doc str>) { self.lhs.visit_union_type(node); self.rhs.visit_union_type(node); } #[inline] fn visit_enum_type(&mut self, node: &'doc schema::EnumType<'doc, &'doc str>) { self.lhs.visit_enum_type(node); self.rhs.visit_enum_type(node); } #[inline] fn visit_input_object_type(&mut self, node: &'doc schema::InputObjectType<'doc, &'doc str>) { self.lhs.visit_input_object_type(node); self.rhs.visit_input_object_type(node); } #[inline] fn visit_type_extension(&mut self, node: &'doc schema::TypeExtension<'doc, &'doc str>) { self.lhs.visit_type_extension(node); self.rhs.visit_type_extension(node); } #[inline] fn visit_scalar_type_extension( &mut self, node: &'doc schema::ScalarTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_scalar_type_extension(node); self.rhs.visit_scalar_type_extension(node); } #[inline] fn visit_object_type_extension( &mut self, node: &'doc schema::ObjectTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_object_type_extension(node); self.rhs.visit_object_type_extension(node); } #[inline] fn visit_interface_type_extension( &mut self, node: &'doc schema::InterfaceTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_interface_type_extension(node); self.rhs.visit_interface_type_extension(node); } #[inline] fn visit_union_type_extension( &mut self, node: &'doc schema::UnionTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_union_type_extension(node); self.rhs.visit_union_type_extension(node); } #[inline] fn visit_enum_type_extension( &mut self, node: &'doc schema::EnumTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_enum_type_extension(node); self.rhs.visit_enum_type_extension(node); } #[inline] fn visit_input_object_type_extension( &mut self, node: &'doc schema::InputObjectTypeExtension<'doc, &'doc str>, ) { self.lhs.visit_input_object_type_extension(node); self.rhs.visit_input_object_type_extension(node); } } ================================================ FILE: juniper-from-schema-code-gen/src/ast_pass/validations.rs ================================================ use std::collections::BTreeSet; use super::{error::Error, schema_visitor::SchemaVisitor, EmitError, ErrorKind}; use graphql_parser::{ schema::{self, *}, Pos, }; use heck::SnakeCase; pub struct FieldNameCaseValidator { pub errors: BTreeSet, } impl FieldNameCaseValidator { pub fn new() -> Self { Self { errors: Default::default(), } } } impl<'doc> SchemaVisitor<'doc> for FieldNameCaseValidator { fn visit_object_type(&mut self, ty: &'doc schema::ObjectType<'doc, &'doc str>) { self.validate_fields(&ty.fields); } fn visit_interface_type(&mut self, ty: &'doc schema::InterfaceType<'doc, &'doc str>) { self.validate_fields(&ty.fields); } fn visit_input_object_type(&mut self, ty: &'doc schema::InputObjectType<'doc, &'doc str>) { for field in &ty.fields { self.validate_field(&field.name, field.position); } } } impl FieldNameCaseValidator { fn validate_fields<'doc>(&mut self, fields: &'doc [Field<'doc, &'doc str>]) { for field in fields { self.validate_field(&field.name, field.position); } } fn validate_field(&mut self, name: &str, pos: Pos) { if is_snake_case(name) { self.errors.emit_error(pos, ErrorKind::FieldNameInSnakeCase); } } } pub struct UuidNameCaseValidator { pub errors: BTreeSet, } impl UuidNameCaseValidator { pub fn new() -> Self { Self { errors: Default::default(), } } } impl<'doc> SchemaVisitor<'doc> for UuidNameCaseValidator { fn visit_scalar_type(&mut self, scalar: &'doc ScalarType<'doc, &'doc str>) { if scalar.name == "UUID" { self.errors .emit_error(scalar.position, ErrorKind::UppercaseUuidScalar); } } } fn is_snake_case(s: &str) -> bool { s.contains('_') && s.to_snake_case() == s } #[cfg(test)] mod test { #[allow(unused_imports)] use super::*; #[test] fn test_is_snake_case() { assert!(is_snake_case("foo_bar")); assert!(is_snake_case("foo_bar_baz")); assert!(!is_snake_case("foo")); assert!(!is_snake_case("fooBar")); assert!(!is_snake_case("FooBar")); } } ================================================ FILE: juniper-from-schema-code-gen/src/lib.rs ================================================ //! See the docs for "juniper-from-schema" for more info about this. #![deny( unused_imports, mutable_borrow_reservation_conflict, dead_code, unused_variables, unused_must_use )] #![recursion_limit = "256"] #![doc(html_root_url = "https://docs.rs/juniper-from-schema-code-gen/0.5.2")] mod ast_pass; use ast_pass::{code_gen_pass::CodeGenPass, error, AstData}; use graphql_parser::parse_schema; use proc_macro2::Span; use quote::quote; use std::{ fmt, path::{Path, PathBuf}, }; const DATE_TIME_SCALAR_NAME: &str = "DateTimeUtc"; const DATE_SCALAR_NAME: &str = "Date"; const UUID_SCALAR_NAME: &str = "Uuid"; const URL_SCALAR_NAME: &str = "Url"; #[derive(Debug)] pub struct CodeGen { schema: SchemaLocation, context_type: syn::Type, error_type: syn::Type, } impl CodeGen { pub fn build_from_schema_file(path: PathBuf) -> CodeGenBuilder { CodeGenBuilder { schema: SchemaLocation::File(path), context_type: None, error_type: None, } } pub fn build_from_schema_literal(schema: String) -> CodeGenBuilder { CodeGenBuilder { schema: SchemaLocation::Literal(schema), context_type: None, error_type: None, } } pub fn generate_code(self) -> Result { let (schema, schema_path) = match self.schema { SchemaLocation::File(path) => ( std::fs::read_to_string(&path).map_err(Error::Io)?, Some(path), ), SchemaLocation::Literal(schema) => (schema, None), }; let doc = match parse_schema(&schema) { Ok(doc) => doc, Err(parse_error) => return Err(Error::SchemaParseError(parse_error)), }; let ast_data = match AstData::new_from_doc(&doc) { Ok(x) => x, Err(code_gen_errors) => { let errors = Error::CodeGenErrors { errors: code_gen_errors.into_iter().collect(), schema, }; return Err(errors); } }; let output = CodeGenPass::new(&schema, &self.error_type, &self.context_type, ast_data); match output.gen_juniper_code(&doc) { Ok(mut tokens) => { if debugging_enabled() { eprintln!("{}", tokens); } if let Some(path) = schema_path { include_literal_schema(&mut tokens, path.as_path()); } Ok(tokens) } Err(code_gen_errors) => { let errors = Error::CodeGenErrors { errors: code_gen_errors.into_iter().collect(), schema, }; Err(errors) } } } } // This should cause the Rust schema to be rebuild even if the user only changes the GraphQL schema // file. fn include_literal_schema(tokens: &mut proc_macro2::TokenStream, schema_path: &Path) { let schema_path = syn::LitStr::new( schema_path .to_str() .expect("Invalid UTF-8 characters in file name"), Span::call_site(), ); tokens.extend(quote! { const _: &str = std::include_str!(#schema_path); }); } #[derive(Debug)] pub enum Error { SchemaParseError(graphql_parser::schema::ParseError), CodeGenErrors { errors: Vec, schema: String, }, Io(std::io::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::SchemaParseError(inner) => write!(f, "{}", inner), Error::CodeGenErrors { errors, schema } => { assert!( !errors.is_empty(), "`print_and_panic_if_errors` called without any errors" ); let count = errors.len(); let out = errors .iter() .map(|error| error.display(&schema).to_string()) .collect::>() .join("\n\n"); if count == 1 { write!(f, "\n\n{}\n\naborting due to previous error\n", out) } else { write!(f, "\n\n{}\n\naborting due to {} errors\n", out, count) } } Error::Io(inner) => write!(f, "{}", inner), } } } impl std::error::Error for Error {} #[derive(Debug)] pub struct CodeGenBuilder { schema: SchemaLocation, context_type: Option, error_type: Option, } impl CodeGenBuilder { pub fn context_type(mut self, context_type: syn::Type) -> Self { self.context_type = Some(context_type); self } pub fn error_type(mut self, error_type: syn::Type) -> Self { self.error_type = Some(error_type); self } pub fn finish(self) -> CodeGen { CodeGen { schema: self.schema, context_type: self.context_type.unwrap_or_else(default_context_type), error_type: self.error_type.unwrap_or_else(default_error_type), } } } #[derive(Debug)] enum SchemaLocation { File(PathBuf), Literal(String), } pub fn default_error_type() -> syn::Type { syn::parse_str("juniper::FieldError").expect("Failed to parse default error type") } pub fn default_context_type() -> syn::Type { syn::parse_str("Context").expect("Failed to parse default context type") } fn debugging_enabled() -> bool { std::env::var("JUNIPER_FROM_SCHEMA_DEBUG") .map(|val| val == "1") .unwrap_or(false) } ================================================ FILE: juniper-from-schema-proc-macro/Cargo.toml ================================================ [package] version = "0.5.2" authors = ["David Pedersen "] categories = ["web-programming"] description = "Internal code generation crate for juniper-from-schema" documentation = "https://docs.rs/juniper-from-schema-proc-macro" edition = "2018" homepage = "https://github.com/davidpdrsn/juniper-from-schema" keywords = ["web", "graphql", "juniper"] license = "MIT" name = "juniper-from-schema-proc-macro" readme = "README.md" repository = "https://github.com/davidpdrsn/juniper-from-schema.git" [dependencies] juniper-from-schema-code-gen = { version = "0.5.2", path = "../juniper-from-schema-code-gen" } syn = { version = "1", features = ["extra-traits"] } [dev_dependencies] version-sync = "0.8" [lib] proc-macro = true path = "src/lib.rs" ================================================ FILE: juniper-from-schema-proc-macro/README.md ================================================ # juniper-from-schema-proc-macro Internal crate for [juniper-from-schema](https://crates.io/crates/juniper-from-schema). You shouldn't have to depend on this crate directly. The procedural macros are re-exported by [juniper-from-schema](https://crates.io/crates/juniper-from-schema). ================================================ FILE: juniper-from-schema-proc-macro/src/lib.rs ================================================ //! See the docs for "juniper-from-schema" for more info about this. #![deny( unused_imports, mutable_borrow_reservation_conflict, dead_code, unused_variables, unused_must_use )] #![recursion_limit = "256"] #![doc(html_root_url = "https://docs.rs/juniper-from-schema-proc-macro/0.5.2")] mod parse_input; use juniper_from_schema_code_gen::CodeGen; use parse_input::GraphqlSchemaFromFileInput; /// Read a GraphQL schema file and generate corresponding Juniper macro calls. /// /// See [the crate level docs](index.html) for an example. #[proc_macro] pub fn graphql_schema_from_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let GraphqlSchemaFromFileInput { schema_path, context_type, error_type, } = match syn::parse::(input) { Ok(p) => p, Err(e) => return e.to_compile_error().into(), }; let mut builder = CodeGen::build_from_schema_file(schema_path); if let Some(context_type) = context_type { builder = builder.context_type(context_type); } if let Some(error_type) = error_type { builder = builder.error_type(error_type); } let code_gen = builder.finish(); match code_gen.generate_code() { Ok(tokens) => tokens.into(), Err(errors) => panic!("{}", errors), } } /// Write your GraphQL schema directly in your Rust code. /// /// This is mostly useful for testing. Prefer using [`graphql_schema_from_file`][] for larger /// schemas. /// /// [`graphql_schema_from_file`]: macro.graphql_schema_from_file.html /// /// # Example /// /// ```ignore /// graphql_schema! { /// schema { /// query: Query /// } /// /// type Query { /// helloWorld: String! @juniper(ownership: "owned") /// } /// } /// /// pub struct Query; /// /// impl QueryFields for Query { /// fn field_hello_world( /// &self, /// executor: &Executor, /// ) -> FieldResult { /// Ok("Hello, World!".to_string()) /// } /// } /// ``` #[proc_macro] pub fn graphql_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let schema = input.to_string(); let code_gen = CodeGen::build_from_schema_literal(schema).finish(); match code_gen.generate_code() { Ok(tokens) => tokens.into(), Err(errors) => panic!("{}", errors), } } ================================================ FILE: juniper-from-schema-proc-macro/src/parse_input.rs ================================================ use std::{fmt::Write, path::PathBuf}; use syn::{ self, parse::{Parse, ParseStream}, Ident, Token, Type, }; #[derive(Debug)] pub struct GraphqlSchemaFromFileInput { pub schema_path: PathBuf, pub error_type: Option, pub context_type: Option, } impl Parse for GraphqlSchemaFromFileInput { fn parse(input: ParseStream) -> syn::Result { let file = input.parse::()?.value(); let cargo_dir = std::env::var("CARGO_MANIFEST_DIR").expect("Env var `CARGO_MANIFEST_DIR` was missing"); let pwd = PathBuf::from(cargo_dir); let schema_path = pwd.join(file); if input.peek(Token![,]) { input.parse::()?; } let mut error_type = None::; let mut context_type = None::; loop { if input.is_empty() { break; } let key = input.parse::()?; match &*key.to_string() { "error_type" => { input.parse::()?; error_type = Some(input.parse()?); } "context_type" => { input.parse::()?; context_type = Some(input.parse()?); } other => { let mut msg = String::new(); writeln!(msg, "Unknown `graphql_schema_from_file` config `{}`", other).unwrap(); writeln!(msg, "Supported configs are `error_type` and `context_type`").unwrap(); return Err(syn::parse::Error::new(key.span(), msg)); } } if input.peek(Token![,]) { input.parse::()?; } } Ok(GraphqlSchemaFromFileInput { schema_path, error_type, context_type, }) } } ================================================ FILE: juniper-from-schema-proc-macro/tests/version-numbers.rs ================================================ #[macro_use] extern crate version_sync; #[test] fn test_html_root_url() { assert_html_root_url_updated!("src/lib.rs"); } ================================================ FILE: rustfmt.toml ================================================ edition = "2018"