Merge branch 'graphql-rust:master' into impl-hashset-as-vec

This commit is contained in:
mx 2022-09-28 16:47:41 +13:00 committed by GitHub
commit 601bda784e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
327 changed files with 13156 additions and 5727 deletions

View file

@ -21,15 +21,16 @@ jobs:
################ ################
pr: pr:
if: ${{ github.event_name == 'pull_request' if: ${{ github.event_name == 'pull_request' }}
&& !contains(github.event.head_commit.message, '[skip ci]') }}
needs: needs:
- bench
- clippy - clippy
- example - example
- feature - feature
- release-check - release-check
- rustfmt - rustfmt
- test - test
- test-book
- wasm - wasm
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -43,9 +44,6 @@ jobs:
########################## ##########################
clippy: clippy:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -58,9 +56,6 @@ jobs:
- run: make cargo.lint - run: make cargo.lint
rustfmt: rustfmt:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -79,10 +74,20 @@ jobs:
# Testing # # Testing #
########### ###########
bench:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: cargo clippy -p juniper_benchmarks --benches -- -D warnings
- run: cargo bench -p juniper_benchmarks
example: example:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -111,9 +116,6 @@ jobs:
- run: cargo check -p example_${{ matrix.example }} - run: cargo check -p example_${{ matrix.example }}
feature: feature:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -174,9 +176,6 @@ jobs:
- run: cargo package -p ${{ steps.crate.outputs.NAME }} - run: cargo package -p ${{ steps.crate.outputs.NAME }}
test: test:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -187,7 +186,6 @@ jobs:
- juniper_graphql_ws - juniper_graphql_ws
- juniper_integration_tests - juniper_integration_tests
- juniper_codegen_tests - juniper_codegen_tests
- juniper_book_tests
- juniper_actix - juniper_actix
- juniper_hyper - juniper_hyper
- juniper_iron - juniper_iron
@ -210,14 +208,6 @@ jobs:
os: macOS os: macOS
- crate: juniper_codegen_tests - crate: juniper_codegen_tests
os: windows os: windows
- crate: juniper_book_tests
toolchain: beta
- crate: juniper_book_tests
toolchain: nightly
# TODO: LLVM ERROR: out of memory
- crate: juniper_integration_tests
os: windows
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -229,10 +219,35 @@ jobs:
- run: make test.cargo crate=${{ matrix.crate }} - run: make test.cargo crate=${{ matrix.crate }}
test-book:
name: test Book
strategy:
fail-fast: false
matrix:
os:
- ubuntu
- macOS
# TODO: Re-enable once rust-lang/rust#99466 is fixed:
# https://github.com/rust-lang/rust/issues/99466
#- windows
toolchain:
- stable
- beta
- nightly
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
- run: cargo install mdbook
- run: make test.book
wasm: wasm:
if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper')
|| !contains(github.event.head_commit.message, '[skip ci]') }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -264,9 +279,7 @@ jobs:
release-check: release-check:
name: Check release automation name: Check release automation
if: ${{ !startsWith(github.ref, 'refs/tags/juniper') if: ${{ !startsWith(github.ref, 'refs/tags/juniper') }}
&& (github.ref == 'refs/heads/master'
|| !contains(github.event.head_commit.message, '[skip ci]')) }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -296,12 +309,14 @@ jobs:
release-github: release-github:
name: Release on GitHub name: Release on GitHub
needs: needs:
- bench
- clippy - clippy
- example - example
- feature - feature
- package - package
- rustfmt - rustfmt
- test - test
- test-book
- wasm - wasm
if: ${{ startsWith(github.ref, 'refs/tags/juniper') }} if: ${{ startsWith(github.ref, 'refs/tags/juniper') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -325,7 +340,7 @@ jobs:
- name: Parse CHANGELOG link - name: Parse CHANGELOG link
id: changelog id: changelog
run: echo ::set-output run: echo ::set-output
name=LINK::https://github.com/${{ github.repository }}/blob/${{ steps.crate.outputs.NAME }}%40${{ steps.release.outputs.VERSION }}//${{ steps.crate.outputs.NAME }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.release.outputs.VERSION }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.crate.outputs.NAME }}/CHANGELOG.md) name=LINK::${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.crate.outputs.NAME }}%40${{ steps.release.outputs.VERSION }}//${{ steps.crate.outputs.NAME }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.release.outputs.VERSION }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.crate.outputs.NAME }}/CHANGELOG.md)
- uses: softprops/action-gh-release@v1 - uses: softprops/action-gh-release@v1
env: env:
@ -368,19 +383,17 @@ jobs:
deploy-book: deploy-book:
name: deploy Book name: deploy Book
needs: ["test"] needs: ["test", "test-book"]
if: ${{ github.ref == 'refs/heads/master' if: ${{ github.ref == 'refs/heads/master'
|| startsWith(github.ref, 'refs/tags/juniper@') }} || startsWith(github.ref, 'refs/tags/juniper@') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: peaceiris/actions-mdbook@v1 - uses: peaceiris/actions-mdbook@v1
- run: make book.build out=gh-pages/master - run: make book.build out=gh-pages${{ (github.ref == 'refs/heads/master'
if: ${{ github.ref == 'refs/heads/master' }} && '/master')
|| '' }}
- run: make book.build out=gh-pages
if: ${{ startsWith(github.ref, 'refs/tags/juniper@') }}
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3

View file

@ -17,19 +17,16 @@ Before submitting a PR, you should follow these steps to prevent redundant churn
Consistent formatting is enforced on the CI. Consistent formatting is enforced on the CI.
Before you submit your PR, you should run `cargo fmt` in the root directory. Before you submit your PR, you should run `cargo +nightly fmt --all` in the root directory (or use the `make fmt` shortcut).
Formatting should be run on the **stable** compiler. Formatting should be run on the **nightly** compiler.
(You can do `rustup run stable cargo fmt` when developing on nightly)
### Run all tests ### Run all tests
To run all available tests, including verifying the code examples in the book, To run all available tests, including verifying the code examples in the book:
you can use [cargo-make](https://github.com/sagiegurari/cargo-make).
1. Install cargo-make with `cargo install cargo-make` 1. Run `cargo test` in the root directory.
2. Run `cargo make ci-flow` in the root directory 2. Run `make test.book` in the root directory.
(You can do `rustup run nightly cargo make ci-flow` to run all tests when developing on stable)
### Update the CHANGELOG ### Update the CHANGELOG

View file

@ -1,7 +1,6 @@
[workspace] [workspace]
members = [ members = [
"benches", "benches",
"book/tests",
"examples/basic_subscriptions", "examples/basic_subscriptions",
"examples/warp_async", "examples/warp_async",
"examples/warp_subscriptions", "examples/warp_subscriptions",

View file

@ -87,10 +87,14 @@ cargo.test: test.cargo
# Run Rust tests of Book. # Run Rust tests of Book.
# #
# Usage: # Usage:
# make test.book # make test.book [clean=(no|yes)]
test.book: test.book:
@make test.cargo crate=juniper_book_tests ifeq ($(clean),yes)
cargo clean
endif
cargo build
mdbook test book -L target/debug/deps
# Run Rust tests of project crates. # Run Rust tests of project crates.

View file

@ -47,7 +47,7 @@ see the [actix][actix_examples], [hyper][hyper_examples], [rocket][rocket_exampl
## Features ## Features
Juniper supports the full GraphQL query language according to the Juniper supports the full GraphQL query language according to the
[specification][graphql_spec], including interfaces, unions, schema [specification (October 2021)][graphql_spec], including interfaces, unions, schema
introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language]. introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language].
As an exception to other GraphQL libraries for other languages, Juniper builds As an exception to other GraphQL libraries for other languages, Juniper builds
@ -97,7 +97,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
[graphiql]: https://github.com/graphql/graphiql [graphiql]: https://github.com/graphql/graphiql
[playground]: https://github.com/prisma/graphql-playground [playground]: https://github.com/prisma/graphql-playground
[iron]: https://github.com/iron/iron [iron]: https://github.com/iron/iron
[graphql_spec]: http://facebook.github.io/graphql [graphql_spec]: https://spec.graphql.org/October2021
[schema_language]: https://graphql.org/learn/schema/#type-language [schema_language]: https://graphql.org/learn/schema/#type-language
[schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/ [schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/fixtures/starwars/schema.rs [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/fixtures/starwars/schema.rs

View file

@ -1,7 +1,7 @@
[package] [package]
name = "juniper_benchmarks" name = "juniper_benchmarks"
version = "0.0.0" version = "0.0.0"
edition = "2018" edition = "2021"
authors = ["Christoph Herzog <chris@theduke.at>"] authors = ["Christoph Herzog <chris@theduke.at>"]
publish = false publish = false
@ -10,7 +10,7 @@ futures = "0.3"
juniper = { path = "../juniper" } juniper = { path = "../juniper" }
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.4"
tokio = { version = "1.0", features = ["rt-multi-thread"] } tokio = { version = "1.0", features = ["rt-multi-thread"] }
[[bench]] [[bench]]

View file

@ -1,10 +1,11 @@
use criterion::{criterion_group, criterion_main, Criterion, ParameterizedBenchmark}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use juniper::InputValue; use juniper::InputValue;
use juniper_benchmarks as j; use juniper_benchmarks as j;
fn bench_sync_vs_async_users_flat_instant(c: &mut Criterion) { fn bench_sync_vs_async_users_flat_instant(c: &mut Criterion) {
const ASYNC_QUERY: &'static str = r#" // language=GraphQL
const ASYNC_QUERY: &str = r#"
query Query($id: Int) { query Query($id: Int) {
users_async_instant(ids: [$id]!) { users_async_instant(ids: [$id]!) {
id id
@ -15,74 +16,70 @@ fn bench_sync_vs_async_users_flat_instant(c: &mut Criterion) {
} }
"#; "#;
const SYNC_QUERY: &'static str = r#" // language=GraphQL
query Query($id: Int) { const SYNC_QUERY: &str = r#"
users_sync_instant(ids: [$id]!) { query Query($id: Int) {
id users_sync_instant(ids: [$id]!) {
kind id
username kind
email username
email
}
} }
"#;
let mut group = c.benchmark_group("Sync vs Async - Users Flat - Instant");
for count in [1, 10] {
group.bench_function(BenchmarkId::new("Sync", count), |b| {
let ids = (0..count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
j::execute_sync(
SYNC_QUERY,
vec![("ids".to_owned(), ids.clone())].into_iter().collect(),
)
})
});
group.bench_function(BenchmarkId::new("Async - Single Thread", count), |b| {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let ids = (0..count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
let f = j::execute(
ASYNC_QUERY,
vec![("ids".to_owned(), ids.clone())].into_iter().collect(),
);
rt.block_on(f)
})
});
group.bench_function(BenchmarkId::new("Async - Threadpool", count), |b| {
let rt = tokio::runtime::Builder::new_multi_thread().build().unwrap();
let ids = (0..count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
let f = j::execute(
ASYNC_QUERY,
vec![("ids".to_owned(), ids.clone())].into_iter().collect(),
);
rt.block_on(f)
})
});
} }
"#; group.finish();
c.bench(
"Sync vs Async - Users Flat - Instant",
ParameterizedBenchmark::new(
"Sync",
|b, count| {
let ids = (0..*count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
j::execute_sync(
SYNC_QUERY,
vec![("ids".to_string(), ids.clone())].into_iter().collect(),
)
})
},
vec![1, 10],
)
.with_function("Async - Single Thread", |b, count| {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
let ids = (0..*count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
let f = j::execute(
ASYNC_QUERY,
vec![("ids".to_string(), ids.clone())].into_iter().collect(),
);
rt.block_on(f)
})
})
.with_function("Async - Threadpool", |b, count| {
let mut rt = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap();
let ids = (0..*count)
.map(|x| InputValue::scalar(x as i32))
.collect::<Vec<_>>();
let ids = InputValue::list(ids);
b.iter(|| {
let f = j::execute(
ASYNC_QUERY,
vec![("ids".to_string(), ids.clone())].into_iter().collect(),
);
rt.block_on(f)
})
}),
);
} }
criterion_group!(benches, bench_sync_vs_async_users_flat_instant); criterion_group!(benches, bench_sync_vs_async_users_flat_instant);

View file

@ -11,11 +11,11 @@ pub type QueryResult = Result<
String, String,
>; >;
pub struct Context {} pub struct Context;
impl Context { impl Context {
fn new() -> Self { fn new() -> Self {
Self {} Self
} }
} }
@ -51,8 +51,8 @@ impl User {
Self { Self {
id, id,
kind: UserKind::Admin, kind: UserKind::Admin,
username: "userx".to_string(), username: "userx".into(),
email: "userx@domain.com".to_string(), email: "userx@domain.com".into(),
gender: Some(Gender::Female), gender: Some(Gender::Female),
} }
} }
@ -97,7 +97,7 @@ pub fn new_schema() -> RootNode<'static, Query, EmptyMutation<Context>, EmptySub
pub fn execute_sync(query: &str, vars: Variables) -> QueryResult { pub fn execute_sync(query: &str, vars: Variables) -> QueryResult {
let root = new_schema(); let root = new_schema();
let ctx = Context::new(); let ctx = Context::new();
juniper::execute_sync(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e)) juniper::execute_sync(query, None, &root, &vars, &ctx).map_err(|e| format!("{e:?}"))
} }
pub async fn execute(query: &str, vars: Variables) -> QueryResult { pub async fn execute(query: &str, vars: Variables) -> QueryResult {
@ -105,5 +105,5 @@ pub async fn execute(query: &str, vars: Variables) -> QueryResult {
let ctx = Context::new(); let ctx = Context::new();
juniper::execute(query, None, &root, &vars, &ctx) juniper::execute(query, None, &root, &vars, &ctx)
.await .await
.map_err(|e| format!("{:?}", e)) .map_err(|e| format!("{e:?}"))
} }

View file

@ -47,21 +47,8 @@ The output will be in the `_rendered/` directory.
To run the tests validating all code examples in the book, run: To run the tests validating all code examples in the book, run:
```bash ```bash
cd tests/ mdbook test -L ../target/debug/deps
cargo test
# or from project root dir:
cargo test -p juniper_book_tests
# or via shortcut from project root dir: # or via shortcut from project root dir:
make test.book make test.book
``` ```
## Test setup
All Rust code examples in the book are compiled on the CI.
This is done using the [skeptic](https://github.com/budziq/rust-skeptic) crate.

View file

@ -13,4 +13,4 @@ create-missing = false
git_repository_url = "https://github.com/graphql-rs/juniper" git_repository_url = "https://github.com/graphql-rs/juniper"
[rust] [rust]
edition = "2018" edition = "2021"

View file

@ -21,7 +21,7 @@ embedded [Graphiql][graphiql] for easy debugging.
## Features ## Features
Juniper supports the full GraphQL query language according to the Juniper supports the full GraphQL query language according to the
[specification][graphql_spec], including interfaces, unions, schema [specification (October 2021)][graphql_spec], including interfaces, unions, schema
introspection, and validations. introspection, and validations.
It does not, however, support the schema language. It does not, however, support the schema language.
@ -57,7 +57,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
[graphql]: http://graphql.org [graphql]: http://graphql.org
[graphiql]: https://github.com/graphql/graphiql [graphiql]: https://github.com/graphql/graphiql
[iron]: https://github.com/iron/iron [iron]: https://github.com/iron/iron
[graphql_spec]: http://facebook.github.io/graphql [graphql_spec]: https://spec.graphql.org/October2021
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
[tokio]: https://github.com/tokio-rs/tokio [tokio]: https://github.com/tokio-rs/tokio
[hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples [hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples

View file

@ -68,7 +68,7 @@ use std::env;
pub fn get_db_conn() -> Connection { pub fn get_db_conn() -> Connection {
let pg_connection_string = env::var("DATABASE_URI").expect("need a db uri"); let pg_connection_string = env::var("DATABASE_URI").expect("need a db uri");
println!("Connecting to {}", pg_connection_string); println!("Connecting to {pg_connection_string}");
let conn = Connection::connect(&pg_connection_string[..], TlsMode::None).unwrap(); let conn = Connection::connect(&pg_connection_string[..], TlsMode::None).unwrap();
println!("Connection is fine"); println!("Connection is fine");
conn conn
@ -101,7 +101,7 @@ impl BatchFn<i32, Cult> for CultBatcher {
// A hashmap is used, as we need to return an array which maps each original key to a Cult. // A hashmap is used, as we need to return an array which maps each original key to a Cult.
async fn load(&self, keys: &[i32]) -> HashMap<i32, Cult> { async fn load(&self, keys: &[i32]) -> HashMap<i32, Cult> {
println!("load cult batch {:?}", keys); println!("load cult batch {keys:?}");
let mut cult_hashmap = HashMap::new(); let mut cult_hashmap = HashMap::new();
get_cult_by_ids(&mut cult_hashmap, keys.to_vec()); get_cult_by_ids(&mut cult_hashmap, keys.to_vec());
cult_hashmap cult_hashmap

View file

@ -66,7 +66,7 @@ type Schema = juniper::RootNode<
fn main() { fn main() {
// Create a context object. // Create a context object.
let ctx = Context{}; let ctx = Context;
// Run the built-in introspection query. // Run the built-in introspection query.
let (res, _errors) = juniper::introspect( let (res, _errors) = juniper::introspect(

View file

@ -25,9 +25,11 @@ This example shows a subscription operation that returns two events, the strings
sequentially: sequentially:
```rust ```rust
# use juniper::{graphql_object, graphql_subscription, FieldError}; # extern crate futures;
# use futures::Stream; # extern crate juniper;
# use std::pin::Pin; # use std::pin::Pin;
# use futures::Stream;
# use juniper::{graphql_object, graphql_subscription, FieldError};
# #
# #[derive(Clone)] # #[derive(Clone)]
# pub struct Database; # pub struct Database;
@ -80,7 +82,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
# extern crate juniper; # extern crate juniper;
# extern crate juniper_subscriptions; # extern crate juniper_subscriptions;
# extern crate serde_json; # extern crate serde_json;
# extern crate tokio;
# use juniper::{ # use juniper::{
# http::GraphQLRequest, # http::GraphQLRequest,
# graphql_object, graphql_subscription, # graphql_object, graphql_subscription,
@ -98,7 +99,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
# #
# impl Database { # impl Database {
# fn new() -> Self { # fn new() -> Self {
# Self {} # Self
# } # }
# } # }
# #
@ -126,7 +127,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
type Schema = RootNode<'static, Query, EmptyMutation<Database>, Subscription>; type Schema = RootNode<'static, Query, EmptyMutation<Database>, Subscription>;
fn schema() -> Schema { fn schema() -> Schema {
Schema::new(Query {}, EmptyMutation::new(), Subscription {}) Schema::new(Query, EmptyMutation::new(), Subscription)
} }
async fn run_subscription() { async fn run_subscription() {

View file

@ -130,7 +130,7 @@ impl Mutation {
type Schema = juniper::RootNode<'static, Query, Mutation, EmptySubscription<Context>>; type Schema = juniper::RootNode<'static, Query, Mutation, EmptySubscription<Context>>;
# #
# fn main() { # fn main() {
# let _ = Schema::new(Query, Mutation{}, EmptySubscription::new()); # let _ = Schema::new(Query, Mutation, EmptySubscription::new());
# } # }
``` ```

View file

@ -50,7 +50,7 @@ struct Root;
#[juniper::graphql_object] #[juniper::graphql_object]
impl Root { impl Root {
fn foo() -> String { fn foo() -> String {
"Bar".to_owned() "Bar".into()
} }
} }

View file

@ -77,6 +77,190 @@ struct Human {
``` ```
### Interfaces implementing other interfaces
GraphQL allows implementing interfaces on other interfaces in addition to objects.
```rust
# extern crate juniper;
use juniper::{graphql_interface, graphql_object, ID};
#[graphql_interface(for = [HumanValue, Luke])]
struct Node {
id: ID,
}
#[graphql_interface(impl = NodeValue, for = Luke)]
struct Human {
id: ID,
home_planet: String,
}
struct Luke {
id: ID,
}
#[graphql_object(impl = [HumanValue, NodeValue])]
impl Luke {
fn id(&self) -> &ID {
&self.id
}
// As `String` and `&str` aren't distinguished by
// GraphQL spec, you can use them interchangeably.
// Same is applied for `Cow<'a, str>`.
// ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
fn home_planet() -> &'static str {
"Tatooine"
}
}
#
# fn main() {}
```
> __NOTE:__ Every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile-time error.
```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
pub struct ObjA {
id: String,
}
#[graphql_interface(for = ObjA)]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.'
struct Character {
id: String,
}
fn main() {}
```
### GraphQL subtyping and additional `null`able fields
GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically, this allows you to impose additional bounds on the implementation.
Valid "subtypes" are:
- interface implementer instead of an interface itself:
- `I implements T` in place of a `T`;
- `Vec<I implements T>` in place of a `Vec<T>`.
- non-null value in place of a nullable:
- `T` in place of a `Option<T>`;
- `Vec<T>` in place of a `Vec<Option<T>>`.
These rules are recursively applied, so `Vec<Vec<I implements T>>` is a valid "subtype" of a `Option<Vec<Option<Vec<Option<T>>>>>`.
Also, GraphQL allows implementers to add `null`able fields, which aren't present on an original interface.
```rust
# extern crate juniper;
use juniper::{graphql_interface, graphql_object, ID};
#[graphql_interface(for = [HumanValue, Luke])]
struct Node {
id: ID,
}
#[graphql_interface(for = HumanConnectionValue)]
struct Connection {
nodes: Vec<NodeValue>,
}
#[graphql_interface(impl = NodeValue, for = Luke)]
struct Human {
id: ID,
home_planet: String,
}
#[graphql_interface(impl = ConnectionValue)]
struct HumanConnection {
nodes: Vec<HumanValue>,
// ^^^^^^^^^^ notice not `NodeValue`
// This can happen, because every `Human` is a `Node` too, so we are just
// imposing additional bounds, which still can be resolved with
// `... on Connection { nodes }`.
}
struct Luke {
id: ID,
}
#[graphql_object(impl = [HumanValue, NodeValue])]
impl Luke {
fn id(&self) -> &ID {
&self.id
}
fn home_planet(language: Option<String>) -> &'static str {
// ^^^^^^^^^^^^^^
// Notice additional `null`able field, which is missing on `Human`.
// Resolving `...on Human { homePlanet }` will provide `None` for this
// argument.
match language.as_deref() {
None | Some("en") => "Tatooine",
Some("ko") => "타투인",
_ => todo!(),
}
}
}
#
# fn main() {}
```
Violating GraphQL "subtyping" or additional nullable field rules is a compile-time error.
```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, graphql_object};
pub struct ObjA {
id: String,
}
#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self, is_present: bool) -> &str {
// ^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!`
// isn't present on the interface and so has to be nullable.'
is_present.then_some(&self.id).unwrap_or("missing")
}
}
#[graphql_interface(for = ObjA)]
struct Character {
id: String,
}
#
# fn main() {}
```
```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
id: Vec<String>,
// ^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of
// interface's return object: `[String!]!` is not a subtype of `String!`.'
}
#[graphql_interface(for = ObjA)]
struct Character {
id: String,
}
#
# fn main() {}
```
### Ignoring trait methods ### Ignoring trait methods
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
@ -289,9 +473,9 @@ struct Droid {
[1]: https://spec.graphql.org/June2018/#sec-Interfaces [1]: https://spec.graphql.org/October2021#sec-Interfaces
[2]: https://doc.rust-lang.org/reference/types/trait-object.html [2]: https://doc.rust-lang.org/reference/types/trait-object.html
[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html [3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html [4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html
[5]: https://spec.graphql.org/June2018/#sec-Objects [5]: https://spec.graphql.org/October2021#sec-Objects
[6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html [6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html

View file

@ -157,7 +157,7 @@ They can have custom descriptions and default values.
# extern crate juniper; # extern crate juniper;
# use juniper::graphql_object; # use juniper::graphql_object;
# #
struct Person {} struct Person;
#[graphql_object] #[graphql_object]
impl Person { impl Person {
@ -177,7 +177,7 @@ impl Person {
#[graphql(default)] #[graphql(default)]
arg2: i32, arg2: i32,
) -> String { ) -> String {
format!("{} {}", arg1, arg2) format!("{arg1} {arg2}")
} }
} }
# #

View file

@ -60,7 +60,7 @@ there - those errors are automatically converted into `FieldError`.
## Error payloads, `null`, and partial errors ## Error payloads, `null`, and partial errors
Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability). Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/October2021#sec-Handling-Field-Errors).
When a field returns an error, the field's result is replaced by `null`, an When a field returns an error, the field's result is replaced by `null`, an
additional `errors` object is created at the top level of the response, and the additional `errors` object is created at the top level of the response, and the
@ -168,7 +168,7 @@ impl Example {
# fn main() {} # fn main() {}
``` ```
The specified structured error information is included in the [`extensions`](https://facebook.github.io/graphql/June2018/#sec-Errors) key: The specified structured error information is included in the [`extensions`](https://spec.graphql.org/October2021#sec-Errors) key:
```json ```json
{ {
@ -242,15 +242,15 @@ impl Mutation {
if !(10 <= name.len() && name.len() <= 100) { if !(10 <= name.len() && name.len() <= 100) {
errors.push(ValidationError { errors.push(ValidationError {
field: "name".to_string(), field: "name".into(),
message: "between 10 and 100".to_string() message: "between 10 and 100".into(),
}); });
} }
if !(1 <= quantity && quantity <= 10) { if !(1 <= quantity && quantity <= 10) {
errors.push(ValidationError { errors.push(ValidationError {
field: "quantity".to_string(), field: "quantity".into(),
message: "between 1 and 10".to_string() message: "between 1 and 10".into(),
}); });
} }
@ -338,11 +338,11 @@ impl Mutation {
}; };
if !(10 <= name.len() && name.len() <= 100) { if !(10 <= name.len() && name.len() <= 100) {
error.name = Some("between 10 and 100".to_string()); error.name = Some("between 10 and 100".into());
} }
if !(1 <= quantity && quantity <= 10) { if !(1 <= quantity && quantity <= 10) {
error.quantity = Some("between 1 and 10".to_string()); error.quantity = Some("between 1 and 10".into());
} }
if error.name.is_none() && error.quantity.is_none() { if error.name.is_none() && error.quantity.is_none() {
@ -436,11 +436,11 @@ impl Mutation {
}; };
if !(10 <= name.len() && name.len() <= 100) { if !(10 <= name.len() && name.len() <= 100) {
error.name = Some("between 10 and 100".to_string()); error.name = Some("between 10 and 100".into());
} }
if !(1 <= quantity && quantity <= 10) { if !(1 <= quantity && quantity <= 10) {
error.quantity = Some("between 1 and 10".to_string()); error.quantity = Some("between 1 and 10".into());
} }
if error.name.is_none() && error.quantity.is_none() { if error.name.is_none() && error.quantity.is_none() {

View file

@ -97,6 +97,7 @@ Context cannot be specified by a mutable reference, because concurrent fields re
For example, when using async runtime with [work stealing][2] (like `tokio`), which obviously requires thread safety in addition, you will need to use a corresponding async version of `RwLock`: For example, when using async runtime with [work stealing][2] (like `tokio`), which obviously requires thread safety in addition, you will need to use a corresponding async version of `RwLock`:
```rust ```rust
# extern crate juniper; # extern crate juniper;
# extern crate tokio;
# use std::collections::HashMap; # use std::collections::HashMap;
# use juniper::graphql_object; # use juniper::graphql_object;
use tokio::sync::RwLock; use tokio::sync::RwLock;

View file

@ -24,10 +24,10 @@ Juniper has built-in support for:
* `String` and `&str` as `String` * `String` and `&str` as `String`
* `bool` as `Boolean` * `bool` as `Boolean`
* `juniper::ID` as `ID`. This type is defined [in the * `juniper::ID` as `ID`. This type is defined [in the
spec](http://facebook.github.io/graphql/#sec-ID) as a type that is serialized spec](https://spec.graphql.org/October2021#sec-ID) as a type that is serialized
as a string but can be parsed from both a string and an integer. as a string but can be parsed from both a string and an integer.
Note that there is no built-in support for `i64`/`u64`, as the GraphQL spec [doesn't define any built-in scalars for `i64`/`u64` by default](https://spec.graphql.org/June2018/#sec-Int). You may wish to leverage a [custom GraphQL scalar](#custom-scalars) in your schema to support them. Note that there is no built-in support for `i64`/`u64`, as the GraphQL spec [doesn't define any built-in scalars for `i64`/`u64` by default](https://spec.graphql.org/October2021#sec-Int). You may wish to leverage a [custom GraphQL scalar](#custom-scalars) in your schema to support them.
**Third party types**: **Third party types**:
@ -114,6 +114,7 @@ All the methods used from newtype's field can be replaced with attributes:
### `#[graphql(to_output_with = <fn>)]` attribute ### `#[graphql(to_output_with = <fn>)]` attribute
```rust ```rust
# extern crate juniper;
# use juniper::{GraphQLScalar, ScalarValue, Value}; # use juniper::{GraphQLScalar, ScalarValue, Value};
# #
#[derive(GraphQLScalar)] #[derive(GraphQLScalar)]
@ -131,6 +132,7 @@ fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
### `#[graphql(from_input_with = <fn>)]` attribute ### `#[graphql(from_input_with = <fn>)]` attribute
```rust ```rust
# extern crate juniper;
# use juniper::{GraphQLScalar, InputValue, ScalarValue}; # use juniper::{GraphQLScalar, InputValue, ScalarValue};
# #
#[derive(GraphQLScalar)] #[derive(GraphQLScalar)]
@ -145,14 +147,13 @@ impl UserId {
S: ScalarValue S: ScalarValue
{ {
input.as_string_value() input.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", input)) .ok_or_else(|| format!("Expected `String`, found: {input}"))
.and_then(|str| { .and_then(|str| {
str.strip_prefix("id: ") str.strip_prefix("id: ")
.ok_or_else(|| { .ok_or_else(|| {
format!( format!(
"Expected `UserId` to begin with `id: `, \ "Expected `UserId` to begin with `id: `, \
found: {}", found: {input}",
input,
) )
}) })
}) })
@ -166,6 +167,7 @@ impl UserId {
### `#[graphql(parse_token_with = <fn>]` or `#[graphql(parse_token(<types>)]` attributes ### `#[graphql(parse_token_with = <fn>]` or `#[graphql(parse_token(<types>)]` attributes
```rust ```rust
# extern crate juniper;
# use juniper::{ # use juniper::{
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
# ScalarValue, ScalarToken, Value # ScalarValue, ScalarToken, Value
@ -190,7 +192,7 @@ where
S: ScalarValue S: ScalarValue
{ {
match v { match v {
StringOrInt::String(str) => Value::scalar(str.to_owned()), StringOrInt::String(s) => Value::scalar(s.to_owned()),
StringOrInt::Int(i) => Value::scalar(*i), StringOrInt::Int(i) => Value::scalar(*i),
} }
} }
@ -200,15 +202,12 @@ where
S: ScalarValue S: ScalarValue
{ {
v.as_string_value() v.as_string_value()
.map(|s| StringOrInt::String(s.to_owned())) .map(|s| StringOrInt::String(s.into()))
.or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
} }
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
where
S: ScalarValue
{
<String as ParseScalarValue<S>>::from_str(value) <String as ParseScalarValue<S>>::from_str(value)
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value)) .or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
} }
@ -228,6 +227,7 @@ Path can be simply `with = Self` (default path where macro expects resolvers to
in case there is an impl block with custom resolvers: in case there is an impl block with custom resolvers:
```rust ```rust
# extern crate juniper;
# use juniper::{ # use juniper::{
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
# ScalarValue, ScalarToken, Value # ScalarValue, ScalarToken, Value
@ -243,7 +243,7 @@ enum StringOrInt {
impl StringOrInt { impl StringOrInt {
fn to_output<S: ScalarValue>(&self) -> Value<S> { fn to_output<S: ScalarValue>(&self) -> Value<S> {
match self { match self {
Self::String(str) => Value::scalar(str.to_owned()), Self::String(s) => Value::scalar(s.to_owned()),
Self::Int(i) => Value::scalar(*i), Self::Int(i) => Value::scalar(*i),
} }
} }
@ -253,12 +253,12 @@ impl StringOrInt {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.map(|s| Self::String(s.to_owned())) .map(|s| Self::String(s.into()))
.or_else(|| v.as_int_value().map(Self::Int)) .or_else(|| v.as_int_value().map(Self::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
} }
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<S>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -273,6 +273,7 @@ impl StringOrInt {
Or it can be path to a module, where custom resolvers are located. Or it can be path to a module, where custom resolvers are located.
```rust ```rust
# extern crate juniper;
# use juniper::{ # use juniper::{
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
# ScalarValue, ScalarToken, Value # ScalarValue, ScalarToken, Value
@ -293,7 +294,7 @@ mod string_or_int {
S: ScalarValue, S: ScalarValue,
{ {
match v { match v {
StringOrInt::String(str) => Value::scalar(str.to_owned()), StringOrInt::String(s) => Value::scalar(s.to_owned()),
StringOrInt::Int(i) => Value::scalar(*i), StringOrInt::Int(i) => Value::scalar(*i),
} }
} }
@ -303,12 +304,12 @@ mod string_or_int {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.map(|s| StringOrInt::String(s.to_owned())) .map(|s| StringOrInt::String(s.into()))
.or_else(|| v.as_int_value().map(StringOrInt::Int)) .or_else(|| v.as_int_value().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
} }
pub(super) fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> pub(super) fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<S>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -323,6 +324,7 @@ mod string_or_int {
Also, you can partially override `#[graphql(with)]` attribute with other custom scalars. Also, you can partially override `#[graphql(with)]` attribute with other custom scalars.
```rust ```rust
# extern crate juniper;
# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value};
# #
#[derive(GraphQLScalar)] #[derive(GraphQLScalar)]
@ -338,7 +340,7 @@ impl StringOrInt {
S: ScalarValue, S: ScalarValue,
{ {
match self { match self {
Self::String(str) => Value::scalar(str.to_owned()), Self::String(s) => Value::scalar(s.to_owned()),
Self::Int(i) => Value::scalar(*i), Self::Int(i) => Value::scalar(*i),
} }
} }
@ -348,9 +350,9 @@ impl StringOrInt {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.map(|s| Self::String(s.to_owned())) .map(|s| Self::String(s.into()))
.or_else(|| v.as_int_value().map(Self::Int)) .or_else(|| v.as_int_value().map(Self::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
} }
} }
# #
@ -403,8 +405,8 @@ mod date_scalar {
pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> { pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}")))
} }
} }
# #

View file

@ -130,7 +130,7 @@ impl Character {
# fn main() {} # fn main() {}
``` ```
With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163). With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/October2021#example-f8163).
```rust ```rust
# #![allow(dead_code)] # #![allow(dead_code)]
@ -485,7 +485,7 @@ enum Character {
[1]: https://spec.graphql.org/June2018/#sec-Unions [1]: https://spec.graphql.org/October2021#sec-Unions
[2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html [2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
[5]: https://spec.graphql.org/June2018/#sec-Interfaces [5]: https://spec.graphql.org/October2021#sec-Interfaces
[6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html [6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html

View file

@ -1,22 +0,0 @@
[package]
name = "juniper_book_tests"
version = "0.0.0"
edition = "2018"
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
publish = false
[dependencies]
derive_more = "0.99"
futures = "0.3"
iron = "0.6"
juniper = { path = "../../juniper" }
juniper_iron = { path = "../../juniper_iron" }
juniper_subscriptions = { path = "../../juniper_subscriptions" }
mount = "0.4"
serde_json = "1.0"
skeptic = "0.13"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "sync"] }
uuid = "1.0"
[build-dependencies]
skeptic = "0.13"

View file

@ -1,4 +0,0 @@
fn main() {
let files = skeptic::markdown_files_of_directory("../src/");
skeptic::generate_doc_tests(&files);
}

View file

@ -1,3 +0,0 @@
#![deny(warnings)]
include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs"));

View file

@ -1,7 +1,8 @@
[package] [package]
name = "example_actix_subscriptions" name = "example_actix_subscriptions"
version = "0.0.0" version = "0.0.0"
edition = "2018" edition = "2021"
rust-version = "1.62"
authors = ["Mihai Dinculescu <mihai.dinculescu@outlook.com>"] authors = ["Mihai Dinculescu <mihai.dinculescu@outlook.com>"]
publish = false publish = false

View file

@ -9,9 +9,9 @@ use actix_web::{
}; };
use juniper::{ use juniper::{
graphql_object, graphql_subscription, graphql_value, graphql_subscription, graphql_value,
tests::fixtures::starwars::schema::{Database, Query}, tests::fixtures::starwars::schema::{Database, Query},
EmptyMutation, FieldError, RootNode, EmptyMutation, FieldError, GraphQLObject, RootNode,
}; };
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
use juniper_graphql_ws::ConnectionConfig; use juniper_graphql_ws::ConnectionConfig;
@ -37,23 +37,12 @@ async fn graphql(
struct Subscription; struct Subscription;
#[derive(GraphQLObject)]
struct RandomHuman { struct RandomHuman {
id: String, id: String,
name: String, name: String,
} }
// TODO: remove this when async interfaces are merged
#[graphql_object(context = Database)]
impl RandomHuman {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.name
}
}
type RandomHumanStream = type RandomHumanStream =
Pin<Box<dyn futures::Stream<Item = Result<RandomHuman, FieldError>> + Send>>; Pin<Box<dyn futures::Stream<Item = Result<RandomHuman, FieldError>> + Send>>;
@ -84,8 +73,8 @@ impl Subscription {
let human = context.get_human(&random_id).unwrap().clone(); let human = context.get_human(&random_id).unwrap().clone();
yield Ok(RandomHuman { yield Ok(RandomHuman {
id: human.id().to_owned(), id: human.id().into(),
name: human.name().unwrap().to_owned(), name: human.name().unwrap().into(),
}) })
} }
} }
@ -142,7 +131,7 @@ async fn main() -> std::io::Result<()> {
.finish() .finish()
})) }))
}) })
.bind(format!("{}:{}", "127.0.0.1", 8080))? .bind("127.0.0.1:8080")?
.run() .run()
.await .await
} }

View file

@ -1,7 +1,8 @@
[package] [package]
name = "example_basic_subscriptions" name = "example_basic_subscriptions"
version = "0.0.0" version = "0.0.0"
edition = "2018" edition = "2021"
rust-version = "1.62"
authors = ["Jordao Rosario <jordao.rosario01@gmail.com>"] authors = ["Jordao Rosario <jordao.rosario01@gmail.com>"]
publish = false publish = false

View file

@ -16,7 +16,7 @@ impl juniper::Context for Database {}
impl Database { impl Database {
fn new() -> Self { fn new() -> Self {
Self {} Self
} }
} }
@ -45,7 +45,7 @@ impl Subscription {
type Schema = RootNode<'static, Query, EmptyMutation<Database>, Subscription>; type Schema = RootNode<'static, Query, EmptyMutation<Database>, Subscription>;
fn schema() -> Schema { fn schema() -> Schema {
Schema::new(Query {}, EmptyMutation::new(), Subscription {}) Schema::new(Query, EmptyMutation::new(), Subscription)
} }
#[tokio::main] #[tokio::main]

View file

@ -1,7 +1,8 @@
[package] [package]
name = "example_warp_async" name = "example_warp_async"
version = "0.0.0" version = "0.0.0"
edition = "2018" edition = "2021"
rust-version = "1.62"
authors = ["Christoph Herzog <chris@theduke.at>"] authors = ["Christoph Herzog <chris@theduke.at>"]
publish = false publish = false

View file

@ -1,13 +1,14 @@
[package] [package]
name = "example_warp_subscriptions" name = "example_warp_subscriptions"
version = "0.0.0" version = "0.0.0"
edition = "2018" edition = "2021"
rust-version = "1.62"
publish = false publish = false
[dependencies] [dependencies]
async-stream = "0.3" async-stream = "0.3"
env_logger = "0.9" env_logger = "0.9"
futures = "0.3.1" futures = "0.3"
juniper = { path = "../../juniper" } juniper = { path = "../../juniper" }
juniper_graphql_ws = { path = "../../juniper_graphql_ws" } juniper_graphql_ws = { path = "../../juniper_graphql_ws" }
juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] } juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] }

View file

@ -12,7 +12,7 @@ use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws};
use warp::{http::Response, Filter}; use warp::{http::Response, Filter};
#[derive(Clone)] #[derive(Clone)]
struct Context {} struct Context;
impl juniper::Context for Context {} impl juniper::Context for Context {}
@ -46,7 +46,7 @@ impl User {
async fn friends(&self) -> Vec<User> { async fn friends(&self) -> Vec<User> {
if self.id == 1 { if self.id == 1 {
return vec![ vec![
User { User {
id: 11, id: 11,
kind: UserKind::User, kind: UserKind::User,
@ -62,15 +62,15 @@ impl User {
kind: UserKind::Guest, kind: UserKind::Guest,
name: "user13".into(), name: "user13".into(),
}, },
]; ]
} else if self.id == 2 { } else if self.id == 2 {
return vec![User { vec![User {
id: 21, id: 21,
kind: UserKind::User, kind: UserKind::User,
name: "user21".into(), name: "user21".into(),
}]; }]
} else if self.id == 3 { } else if self.id == 3 {
return vec![ vec![
User { User {
id: 31, id: 31,
kind: UserKind::User, kind: UserKind::User,
@ -81,9 +81,9 @@ impl User {
kind: UserKind::Guest, kind: UserKind::Guest,
name: "user32".into(), name: "user32".into(),
}, },
]; ]
} else { } else {
return vec![]; vec![]
} }
} }
} }
@ -123,7 +123,7 @@ impl Subscription {
yield Ok(User { yield Ok(User {
id: counter, id: counter,
kind: UserKind::Admin, kind: UserKind::Admin,
name: "stream user".to_string(), name: "stream user".into(),
}) })
} }
} }
@ -149,11 +149,11 @@ async fn main() {
let homepage = warp::path::end().map(|| { let homepage = warp::path::end().map(|| {
Response::builder() Response::builder()
.header("content-type", "text/html") .header("content-type", "text/html")
.body("<html><h1>juniper_subscriptions demo</h1><div>visit <a href=\"/playground\">graphql playground</a></html>".to_string()) .body("<html><h1>juniper_subscriptions demo</h1><div>visit <a href=\"/playground\">graphql playground</a></html>")
}); });
let qm_schema = schema(); let qm_schema = schema();
let qm_state = warp::any().map(move || Context {}); let qm_state = warp::any().map(|| Context);
let qm_graphql_filter = juniper_warp::make_graphql_filter(qm_schema, qm_state.boxed()); let qm_graphql_filter = juniper_warp::make_graphql_filter(qm_schema, qm_state.boxed());
let root_node = Arc::new(schema()); let root_node = Arc::new(schema());
@ -165,10 +165,10 @@ async fn main() {
.map(move |ws: warp::ws::Ws| { .map(move |ws: warp::ws::Ws| {
let root_node = root_node.clone(); let root_node = root_node.clone();
ws.on_upgrade(move |websocket| async move { ws.on_upgrade(move |websocket| async move {
serve_graphql_ws(websocket, root_node, ConnectionConfig::new(Context {})) serve_graphql_ws(websocket, root_node, ConnectionConfig::new(Context))
.map(|r| { .map(|r| {
if let Err(e) = r { if let Err(e) = r {
println!("Websocket error: {}", e); println!("Websocket error: {e}");
} }
}) })
.await .await

View file

@ -27,10 +27,11 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Removed support for `dyn` attribute argument (interface values as trait objects). - Removed support for `dyn` attribute argument (interface values as trait objects).
- Removed support for `downcast` attribute argument (custom resolution into implementer types). - Removed support for `downcast` attribute argument (custom resolution into implementer types).
- Removed support for `async` trait methods (not required anymore). - Removed support for `async` trait methods (not required anymore).
- Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). ([#113])
- Forbade default implementations of non-ignored trait methods. - Forbade default implementations of non-ignored trait methods.
- Supported coercion of additional `null`able arguments and return sub-typing on implementer. - Supported coercion of additional `null`able arguments and return sub-typing on implementer.
- Supported `rename_all = "<policy>"` attribute argument influencing all its fields and their arguments. ([#971]) - Supported `rename_all = "<policy>"` attribute argument influencing all its fields and their arguments. ([#971])
- Supported interfaces implementing other interfaces. ([#1028])
- Split `#[derive(GraphQLScalarValue)]` macro into: - Split `#[derive(GraphQLScalarValue)]` macro into:
- `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017])
- Supported generic `ScalarValue`. - Supported generic `ScalarValue`.
@ -47,6 +48,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010]) - Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010])
- Disabled `chrono` [Cargo feature] by default. - Disabled `chrono` [Cargo feature] by default.
- Removed `scalar-naivetime` [Cargo feature]. - Removed `scalar-naivetime` [Cargo feature].
- Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#1081], [#528])
### Added ### Added
@ -70,8 +72,13 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Unsupported expressions in `graphql_value!` macro. ([#996], [#503]) - Unsupported expressions in `graphql_value!` macro. ([#996], [#503])
- Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004]) - Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004])
- All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051])
- Incorrect input value coercion with defaults. ([#1080], [#1073])
- Incorrect error when explicit `null` provided for `null`able list input parameter. ([#1086], [#1085])
- Stack overflow on nested GraphQL fragments. ([CVE-2022-31173])
[#113]: /../../issues/113
[#503]: /../../issues/503 [#503]: /../../issues/503
[#528]: /../../issues/528
[#750]: /../../issues/750 [#750]: /../../issues/750
[#798]: /../../issues/798 [#798]: /../../issues/798
[#918]: /../../issues/918 [#918]: /../../issues/918
@ -94,11 +101,18 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1017]: /../../pull/1017 [#1017]: /../../pull/1017
[#1025]: /../../pull/1025 [#1025]: /../../pull/1025
[#1026]: /../../pull/1026 [#1026]: /../../pull/1026
[#1028]: /../../pull/1028
[#1051]: /../../issues/1051 [#1051]: /../../issues/1051
[#1054]: /../../pull/1054 [#1054]: /../../pull/1054
[#1057]: /../../pull/1057 [#1057]: /../../pull/1057
[#1060]: /../../pull/1060 [#1060]: /../../pull/1060
[#1073]: /../../issues/1073
[#1080]: /../../pull/1080
[#1081]: /../../pull/1081
[#1085]: /../../issues/1085
[#1086]: /../../pull/1086
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j

View file

@ -1,7 +1,8 @@
[package] [package]
name = "juniper" name = "juniper"
version = "0.16.0-dev" version = "0.16.0-dev"
edition = "2018" edition = "2021"
rust-version = "1.62"
description = "GraphQL server library." description = "GraphQL server library."
license = "BSD-2-Clause" license = "BSD-2-Clause"
authors = [ authors = [
@ -38,11 +39,11 @@ schema-language = ["graphql-parser"]
anyhow = { version = "1.0.32", default-features = false, optional = true } anyhow = { version = "1.0.32", default-features = false, optional = true }
async-trait = "0.1.39" async-trait = "0.1.39"
bigdecimal = { version = "0.3", optional = true } bigdecimal = { version = "0.3", optional = true }
bson = { version = "2.0", features = ["chrono-0_4"], optional = true } bson = { version = "2.4", features = ["chrono-0_4"], optional = true }
chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true } chrono = { version = "0.4.20", features = ["alloc"], default-features = false, optional = true }
chrono-tz = { version = "0.6", default-features = false, optional = true } chrono-tz = { version = "0.6", default-features = false, optional = true }
fnv = "1.0.3" fnv = "1.0.3"
futures = { version = "0.3.1", features = ["alloc"], default-features = false } futures = { version = "0.3.22", features = ["alloc"], default-features = false }
futures-enum = { version = "0.1.12", default-features = false } futures-enum = { version = "0.1.12", default-features = false }
graphql-parser = { version = "0.4", optional = true } graphql-parser = { version = "0.4", optional = true }
indexmap = { version = "1.0", features = ["serde-1"] } indexmap = { version = "1.0", features = ["serde-1"] }
@ -58,12 +59,10 @@ uuid = { version = "1.0", default-features = false, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
# not used, to fix `bson` compilation only
uuid_08 = { version = "0.8", package = "uuid", default-features = false, features = ["wasm-bindgen"] }
[dev-dependencies] [dev-dependencies]
bencher = "0.1.2" bencher = "0.1.2"
chrono = { version = "0.4", features = ["alloc"], default-features = false } chrono = { version = "0.4.20", features = ["alloc"], default-features = false }
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
serde_json = "1.0.2" serde_json = "1.0.2"
tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] } tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] }

View file

@ -114,7 +114,7 @@ pub struct Directive<'a, S> {
} }
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperationType { pub enum OperationType {
Query, Query,
Mutation, Mutation,
@ -224,10 +224,10 @@ impl<'a> Type<'a> {
impl<'a> fmt::Display for Type<'a> { impl<'a> fmt::Display for Type<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Named(n) => write!(f, "{}", n), Self::Named(n) => write!(f, "{n}"),
Self::NonNullNamed(n) => write!(f, "{}!", n), Self::NonNullNamed(n) => write!(f, "{n}!"),
Self::List(t, _) => write!(f, "[{}]", t), Self::List(t, _) => write!(f, "[{t}]"),
Self::NonNullList(t, _) => write!(f, "[{}]!", t), Self::NonNullList(t, _) => write!(f, "[{t}]!"),
} }
} }
} }
@ -248,12 +248,12 @@ impl<S> InputValue<S> {
/// Construct an enum value. /// Construct an enum value.
pub fn enum_value<T: AsRef<str>>(s: T) -> Self { pub fn enum_value<T: AsRef<str>>(s: T) -> Self {
Self::Enum(s.as_ref().to_owned()) Self::Enum(s.as_ref().into())
} }
/// Construct a variable value. /// Construct a variable value.
pub fn variable<T: AsRef<str>>(v: T) -> Self { pub fn variable<T: AsRef<str>>(v: T) -> Self {
Self::Variable(v.as_ref().to_owned()) Self::Variable(v.as_ref().into())
} }
/// Construct a [`Spanning::unlocated`] list. /// Construct a [`Spanning::unlocated`] list.
@ -282,7 +282,7 @@ impl<S> InputValue<S> {
o.into_iter() o.into_iter()
.map(|(k, v)| { .map(|(k, v)| {
( (
Spanning::unlocated(k.as_ref().to_owned()), Spanning::unlocated(k.as_ref().into()),
Spanning::unlocated(v), Spanning::unlocated(v),
) )
}) })
@ -295,25 +295,36 @@ impl<S> InputValue<S> {
Self::Object(o) Self::Object(o)
} }
/// Resolve all variables to their values. /// Resolves all variables of this [`InputValue`] to their actual `values`.
///
/// If a variable is not present in the `values`:
/// - Returns [`None`] in case this is an [`InputValue::Variable`].
/// - Skips field in case of an [`InputValue::Object`] field.
/// - Replaces with an [`InputValue::Null`] in case of an
/// [`InputValue::List`] element.
///
/// This is done, because for an [`InputValue::Variable`] (or an
/// [`InputValue::Object`] field) a default value can be used later, if it's
/// provided. While on contrary, a single [`InputValue::List`] element
/// cannot have a default value.
#[must_use] #[must_use]
pub fn into_const(self, vars: &Variables<S>) -> Self pub fn into_const(self, values: &Variables<S>) -> Option<Self>
where where
S: Clone, S: Clone,
{ {
match self { match self {
Self::Variable(v) => vars.get(&v).map_or_else(InputValue::null, Clone::clone), Self::Variable(v) => values.get(&v).cloned(),
Self::List(l) => Self::List( Self::List(l) => Some(Self::List(
l.into_iter() l.into_iter()
.map(|s| s.map(|v| v.into_const(vars))) .map(|s| s.map(|v| v.into_const(values).unwrap_or_else(Self::null)))
.collect(), .collect(),
), )),
Self::Object(o) => Self::Object( Self::Object(o) => Some(Self::Object(
o.into_iter() o.into_iter()
.map(|(sk, sv)| (sk, sv.map(|v| v.into_const(vars)))) .filter_map(|(sk, sv)| sv.and_then(|v| v.into_const(values)).map(|sv| (sk, sv)))
.collect(), .collect(),
), )),
v => v, v => Some(v),
} }
} }
@ -456,13 +467,13 @@ impl<S: ScalarValue> fmt::Display for InputValue<S> {
Self::Null => write!(f, "null"), Self::Null => write!(f, "null"),
Self::Scalar(s) => { Self::Scalar(s) => {
if let Some(s) = s.as_str() { if let Some(s) = s.as_str() {
write!(f, "\"{}\"", s) write!(f, "\"{s}\"")
} else { } else {
write!(f, "{}", s) write!(f, "{s}")
} }
} }
Self::Enum(v) => write!(f, "{}", v), Self::Enum(v) => write!(f, "{v}"),
Self::Variable(v) => write!(f, "${}", v), Self::Variable(v) => write!(f, "${v}"),
Self::List(v) => { Self::List(v) => {
write!(f, "[")?; write!(f, "[")?;
for (i, spanning) in v.iter().enumerate() { for (i, spanning) in v.iter().enumerate() {
@ -577,30 +588,30 @@ mod tests {
#[test] #[test]
fn test_input_value_fmt() { fn test_input_value_fmt() {
let value: InputValue = graphql_input_value!(null); let value: InputValue = graphql_input_value!(null);
assert_eq!(format!("{}", value), "null"); assert_eq!(value.to_string(), "null");
let value: InputValue = graphql_input_value!(123); let value: InputValue = graphql_input_value!(123);
assert_eq!(format!("{}", value), "123"); assert_eq!(value.to_string(), "123");
let value: InputValue = graphql_input_value!(12.3); let value: InputValue = graphql_input_value!(12.3);
assert_eq!(format!("{}", value), "12.3"); assert_eq!(value.to_string(), "12.3");
let value: InputValue = graphql_input_value!("FOO"); let value: InputValue = graphql_input_value!("FOO");
assert_eq!(format!("{}", value), "\"FOO\""); assert_eq!(value.to_string(), "\"FOO\"");
let value: InputValue = graphql_input_value!(true); let value: InputValue = graphql_input_value!(true);
assert_eq!(format!("{}", value), "true"); assert_eq!(value.to_string(), "true");
let value: InputValue = graphql_input_value!(BAR); let value: InputValue = graphql_input_value!(BAR);
assert_eq!(format!("{}", value), "BAR"); assert_eq!(value.to_string(), "BAR");
let value: InputValue = graphql_input_value!(@baz); let value: InputValue = graphql_input_value!(@baz);
assert_eq!(format!("{}", value), "$baz"); assert_eq!(value.to_string(), "$baz");
let value: InputValue = graphql_input_value!([1, 2]); let value: InputValue = graphql_input_value!([1, 2]);
assert_eq!(format!("{}", value), "[1, 2]"); assert_eq!(value.to_string(), "[1, 2]");
let value: InputValue = graphql_input_value!({"foo": 1,"bar": 2}); let value: InputValue = graphql_input_value!({"foo": 1,"bar": 2});
assert_eq!(format!("{}", value), "{foo: 1, bar: 2}"); assert_eq!(value.to_string(), "{foo: 1, bar: 2}");
} }
} }

View file

@ -10,7 +10,7 @@ use super::Variables;
/// An enum that describes if a field is available in all types of the interface /// An enum that describes if a field is available in all types of the interface
/// or only in a certain subtype /// or only in a certain subtype
#[derive(Debug, Clone, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Applies<'a> { pub enum Applies<'a> {
/// The field is available independent from the type /// The field is available independent from the type
All, All,
@ -112,12 +112,11 @@ pub struct LookAheadSelection<'a, S: 'a> {
pub(super) children: Vec<ChildSelection<'a, S>>, pub(super) children: Vec<ChildSelection<'a, S>>,
} }
impl<'a, S> Default for LookAheadSelection<'a, S> // Implemented manually to omit redundant `S: Default` trait bound, imposed by
where // `#[derive(Default)]`.
S: ScalarValue, impl<'a, S: 'a> Default for LookAheadSelection<'a, S> {
{
fn default() -> Self { fn default() -> Self {
LookAheadSelection { Self {
name: "", name: "",
alias: None, alias: None,
arguments: vec![], arguments: vec![],

View file

@ -304,7 +304,7 @@ where
type Type; type Type;
#[doc(hidden)] #[doc(hidden)]
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S>; fn into_resolvable(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S>;
} }
impl<'a, S, T, C> IntoResolvable<'a, S, T, C> for T impl<'a, S, T, C> IntoResolvable<'a, S, T, C> for T
@ -315,7 +315,7 @@ where
{ {
type Type = T; type Type = T;
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into_resolvable(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some((FromContext::from(ctx), self))) Ok(Some((FromContext::from(ctx), self)))
} }
} }
@ -328,7 +328,7 @@ where
{ {
type Type = T; type Type = T;
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into_resolvable(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
self.map(|v: T| Some((<T::Context as FromContext<C>>::from(ctx), v))) self.map(|v: T| Some((<T::Context as FromContext<C>>::from(ctx), v)))
.map_err(IntoFieldError::into_field_error) .map_err(IntoFieldError::into_field_error)
} }
@ -341,7 +341,7 @@ where
{ {
type Type = T; type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into_resolvable(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some(self)) Ok(Some(self))
} }
} }
@ -354,7 +354,7 @@ where
type Type = T; type Type = T;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> { fn into_resolvable(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> {
Ok(self.map(|(ctx, v)| (ctx, Some(v)))) Ok(self.map(|(ctx, v)| (ctx, Some(v))))
} }
} }
@ -367,7 +367,7 @@ where
{ {
type Type = T; type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S2> { fn into_resolvable(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S2> {
self.map(Some).map_err(FieldError::map_scalar_value) self.map(Some).map_err(FieldError::map_scalar_value)
} }
} }
@ -382,7 +382,7 @@ where
type Type = T; type Type = T;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S2> { fn into_resolvable(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S2> {
self.map(|o| o.map(|(ctx, v)| (ctx, Some(v)))) self.map(|o| o.map(|(ctx, v)| (ctx, Some(v))))
.map_err(FieldError::map_scalar_value) .map_err(FieldError::map_scalar_value)
} }
@ -774,7 +774,7 @@ impl<'a> FieldPath<'a> {
FieldPath::Root(_) => (), FieldPath::Root(_) => (),
FieldPath::Field(name, _, parent) => { FieldPath::Field(name, _, parent) => {
parent.construct_path(acc); parent.construct_path(acc);
acc.push((*name).to_owned()); acc.push((*name).into());
} }
} }
} }
@ -791,7 +791,7 @@ impl<S> ExecutionError<S> {
pub fn new(location: SourcePosition, path: &[&str], error: FieldError<S>) -> ExecutionError<S> { pub fn new(location: SourcePosition, path: &[&str], error: FieldError<S>) -> ExecutionError<S> {
ExecutionError { ExecutionError {
location, location,
path: path.iter().map(|s| (*s).to_owned()).collect(), path: path.iter().map(|s| (*s).into()).collect(),
error, error,
} }
} }
@ -814,13 +814,13 @@ impl<S> ExecutionError<S> {
/// Create new `Executor` and start query/mutation execution. /// Create new `Executor` and start query/mutation execution.
/// Returns `IsSubscription` error if subscription is passed. /// Returns `IsSubscription` error if subscription is passed.
pub fn execute_validated_query<'a, 'b, QueryT, MutationT, SubscriptionT, S>( pub fn execute_validated_query<'b, QueryT, MutationT, SubscriptionT, S>(
document: &'b Document<S>, document: &'b Document<S>,
operation: &'b Spanning<Operation<S>>, operation: &'b Spanning<Operation<S>>,
root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>, root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where where
S: ScalarValue, S: ScalarValue,
QueryT: GraphQLType<S>, QueryT: GraphQLType<S>,
@ -842,10 +842,10 @@ where
defs.item defs.item
.items .items
.iter() .iter()
.filter_map(|&(ref name, ref def)| { .filter_map(|(name, def)| {
def.default_value def.default_value
.as_ref() .as_ref()
.map(|i| (name.item.to_owned(), i.item.clone())) .map(|i| (name.item.into(), i.item.clone()))
}) })
.collect::<HashMap<String, InputValue<S>>>() .collect::<HashMap<String, InputValue<S>>>()
}); });
@ -914,7 +914,7 @@ pub async fn execute_validated_query_async<'a, 'b, QueryT, MutationT, Subscripti
root_node: &RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where where
QueryT: GraphQLTypeAsync<S>, QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync, QueryT::TypeInfo: Sync,
@ -943,7 +943,7 @@ where
.filter_map(|&(ref name, ref def)| { .filter_map(|&(ref name, ref def)| {
def.default_value def.default_value
.as_ref() .as_ref()
.map(|i| (name.item.to_owned(), i.item.clone())) .map(|i| (name.item.into(), i.item.clone()))
}) })
.collect::<HashMap<String, InputValue<S>>>() .collect::<HashMap<String, InputValue<S>>>()
}); });
@ -1011,10 +1011,10 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub fn get_operation<'b, 'd, 'e, S>( pub fn get_operation<'b, 'd, S>(
document: &'b Document<'d, S>, document: &'b Document<'d, S>,
operation_name: Option<&str>, operation_name: Option<&str>,
) -> Result<&'b Spanning<Operation<'d, S>>, GraphQLError<'e>> ) -> Result<&'b Spanning<Operation<'d, S>>, GraphQLError>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -1058,7 +1058,7 @@ pub async fn resolve_validated_subscription<
root_node: &'r RootNode<'r, QueryT, MutationT, SubscriptionT, S>, root_node: &'r RootNode<'r, QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &'r QueryT::Context, context: &'r QueryT::Context,
) -> Result<(Value<ValuesStream<'r, S>>, Vec<ExecutionError<S>>), GraphQLError<'r>> ) -> Result<(Value<ValuesStream<'r, S>>, Vec<ExecutionError<S>>), GraphQLError>
where where
'r: 'exec_ref, 'r: 'exec_ref,
'd: 'r, 'd: 'r,
@ -1090,7 +1090,7 @@ where
.filter_map(|&(ref name, ref def)| { .filter_map(|&(ref name, ref def)| {
def.default_value def.default_value
.as_ref() .as_ref()
.map(|i| (name.item.to_owned(), i.item.clone())) .map(|i| (name.item.into(), i.item.clone()))
}) })
.collect::<HashMap<String, InputValue<S>>>() .collect::<HashMap<String, InputValue<S>>>()
}); });
@ -1172,7 +1172,7 @@ impl<'r, S: 'r> Registry<'r, S> {
if !self.types.contains_key(name) { if !self.types.contains_key(name) {
self.insert_placeholder( self.insert_placeholder(
validated_name.clone(), validated_name.clone(),
Type::NonNullNamed(Cow::Owned(name.to_string())), Type::NonNullNamed(Cow::Owned(name.into())),
); );
let meta = T::meta(info, self); let meta = T::meta(info, self);
self.types.insert(validated_name, meta); self.types.insert(validated_name, meta);
@ -1209,7 +1209,7 @@ impl<'r, S: 'r> Registry<'r, S> {
S: ScalarValue, S: ScalarValue,
{ {
Field { Field {
name: smartstring::SmartString::from(name), name: name.into(),
description: None, description: None,
arguments: None, arguments: None,
field_type: self.get_type::<I>(info), field_type: self.get_type::<I>(info),
@ -1227,9 +1227,6 @@ impl<'r, S: 'r> Registry<'r, S> {
} }
/// Creates an [`Argument`] with the provided default `value`. /// Creates an [`Argument`] with the provided default `value`.
///
/// When called with type `T`, the actual [`Argument`] will be given the
/// type `Option<T>`.
pub fn arg_with_default<T>( pub fn arg_with_default<T>(
&mut self, &mut self,
name: &str, name: &str,
@ -1240,7 +1237,7 @@ impl<'r, S: 'r> Registry<'r, S> {
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>, T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>,
S: ScalarValue, S: ScalarValue,
{ {
Argument::new(name, self.get_type::<Option<T>>(info)).default_value(value.to_input_value()) Argument::new(name, self.get_type::<T>(info)).default_value(value.to_input_value())
} }
fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) { fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) {
@ -1258,7 +1255,7 @@ impl<'r, S: 'r> Registry<'r, S> {
{ {
let name = T::name(info).expect("Scalar types must be named. Implement `name()`"); let name = T::name(info).expect("Scalar types must be named. Implement `name()`");
ScalarMeta::new::<T>(Cow::Owned(name.to_string())) ScalarMeta::new::<T>(Cow::Owned(name.into()))
} }
/// Creates a [`ListMeta`] type. /// Creates a [`ListMeta`] type.
@ -1302,7 +1299,7 @@ impl<'r, S: 'r> Registry<'r, S> {
let mut v = fields.to_vec(); let mut v = fields.to_vec();
v.push(self.field::<String>("__typename", &())); v.push(self.field::<String>("__typename", &()));
ObjectMeta::new(Cow::Owned(name.to_string()), &v) ObjectMeta::new(Cow::Owned(name.into()), &v)
} }
/// Creates an [`EnumMeta`] type out of the provided `values`. /// Creates an [`EnumMeta`] type out of the provided `values`.
@ -1318,7 +1315,7 @@ impl<'r, S: 'r> Registry<'r, S> {
{ {
let name = T::name(info).expect("Enum types must be named. Implement `name()`"); let name = T::name(info).expect("Enum types must be named. Implement `name()`");
EnumMeta::new::<T>(Cow::Owned(name.to_string()), values) EnumMeta::new::<T>(Cow::Owned(name.into()), values)
} }
/// Creates an [`InterfaceMeta`] type with the given `fields`. /// Creates an [`InterfaceMeta`] type with the given `fields`.
@ -1335,7 +1332,7 @@ impl<'r, S: 'r> Registry<'r, S> {
let mut v = fields.to_vec(); let mut v = fields.to_vec();
v.push(self.field::<String>("__typename", &())); v.push(self.field::<String>("__typename", &()));
InterfaceMeta::new(Cow::Owned(name.to_string()), &v) InterfaceMeta::new(Cow::Owned(name.into()), &v)
} }
/// Creates an [`UnionMeta`] type of the given `types`. /// Creates an [`UnionMeta`] type of the given `types`.
@ -1346,7 +1343,7 @@ impl<'r, S: 'r> Registry<'r, S> {
{ {
let name = T::name(info).expect("Union types must be named. Implement name()"); let name = T::name(info).expect("Union types must be named. Implement name()");
UnionMeta::new(Cow::Owned(name.to_string()), types) UnionMeta::new(Cow::Owned(name.into()), types)
} }
/// Creates an [`InputObjectMeta`] type with the given `args`. /// Creates an [`InputObjectMeta`] type with the given `args`.
@ -1362,6 +1359,6 @@ impl<'r, S: 'r> Registry<'r, S> {
{ {
let name = T::name(info).expect("Input object types must be named. Implement name()"); let name = T::name(info).expect("Input object types must be named. Implement name()");
InputObjectMeta::new::<T>(Cow::Owned(name.to_string()), args) InputObjectMeta::new::<T>(Cow::Owned(name.into()), args)
} }
} }

View file

@ -1,5 +1,6 @@
use crate::{ use crate::{
graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLEnum, RootNode, Value, graphql_object, graphql_value, graphql_vars, EmptyMutation, EmptySubscription, GraphQLEnum,
RootNode, Value,
}; };
#[derive(GraphQLEnum)] #[derive(GraphQLEnum)]
@ -30,7 +31,7 @@ impl User {
(0..10) (0..10)
.map(|index| User { .map(|index| User {
id: index, id: index,
name: format!("user{}", index), name: format!("user{index}"),
kind: UserKind::User, kind: UserKind::User,
}) })
.collect() .collect()
@ -55,7 +56,7 @@ impl Query {
} }
async fn field_async_plain() -> String { async fn field_async_plain() -> String {
"field_async_plain".to_string() "field_async_plain".into()
} }
fn user(id: String) -> User { fn user(id: String) -> User {
@ -86,8 +87,7 @@ async fn async_simple() {
} }
"#; "#;
let vars = Default::default(); let (res, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &())
let (res, errs) = crate::execute(doc, None, &schema, &vars, &())
.await .await
.unwrap(); .unwrap();

View file

@ -35,7 +35,7 @@ where
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let obj = result.as_object_value().expect("Result is not an object"); let obj = result.as_object_value().expect("Result is not an object");

View file

@ -22,7 +22,7 @@ struct TestType;
#[crate::graphql_object] #[crate::graphql_object]
impl TestType { impl TestType {
fn to_string(color: Color) -> String { fn to_string(color: Color) -> String {
format!("Color::{:?}", color) format!("Color::{color:?}")
} }
fn a_color() -> Color { fn a_color() -> Color {
@ -46,7 +46,7 @@ where
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let obj = result.as_object_value().expect("Result is not an object"); let obj = result.as_object_value().expect("Result is not an object");

View file

@ -96,7 +96,7 @@ mod field_execution {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -180,7 +180,7 @@ mod merge_parallel_fragments {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -288,7 +288,7 @@ mod merge_parallel_inline_fragments {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -355,7 +355,7 @@ mod threads_context_correctly {
&schema, &schema,
&vars, &vars,
&TestContext { &TestContext {
value: "Context value".to_owned(), value: "Context value".into(),
}, },
) )
.await .await
@ -363,7 +363,7 @@ mod threads_context_correctly {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!({"a": "Context value"})); assert_eq!(result, graphql_value!({"a": "Context value"}));
} }
@ -410,7 +410,7 @@ mod dynamic_context_switching {
let res = context let res = context
.items .items
.get(&key) .get(&key)
.ok_or(format!("Could not find key {}", key)) .ok_or(format!("Could not find key {key}"))
.map(|c| (c, ItemRef))?; .map(|c| (c, ItemRef))?;
Ok(res) Ok(res)
} }
@ -420,7 +420,7 @@ mod dynamic_context_switching {
key: i32, key: i32,
) -> FieldResult<Option<(&InnerContext, ItemRef)>> { ) -> FieldResult<Option<(&InnerContext, ItemRef)>> {
if key > 100 { if key > 100 {
Err(format!("Key too large: {}", key))?; Err(format!("Key too large: {key}"))?;
} }
Ok(context.items.get(&key).map(|c| (c, ItemRef))) Ok(context.items.get(&key).map(|c| (c, ItemRef)))
} }
@ -452,13 +452,13 @@ mod dynamic_context_switching {
( (
0, 0,
InnerContext { InnerContext {
value: "First value".to_owned(), value: "First value".into(),
}, },
), ),
( (
1, 1,
InnerContext { InnerContext {
value: "Second value".to_owned(), value: "Second value".into(),
}, },
), ),
] ]
@ -472,7 +472,7 @@ mod dynamic_context_switching {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -500,13 +500,13 @@ mod dynamic_context_switching {
( (
0, 0,
InnerContext { InnerContext {
value: "First value".to_owned(), value: "First value".into(),
}, },
), ),
( (
1, 1,
InnerContext { InnerContext {
value: "Second value".to_owned(), value: "Second value".into(),
}, },
), ),
] ]
@ -520,7 +520,7 @@ mod dynamic_context_switching {
assert_eq!(errs, vec![]); assert_eq!(errs, vec![]);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); assert_eq!(result, graphql_value!({"first": {"value": "First value"}}));
} }
@ -542,13 +542,13 @@ mod dynamic_context_switching {
( (
0, 0,
InnerContext { InnerContext {
value: "First value".to_owned(), value: "First value".into(),
}, },
), ),
( (
1, 1,
InnerContext { InnerContext {
value: "Second value".to_owned(), value: "Second value".into(),
}, },
), ),
] ]
@ -569,7 +569,7 @@ mod dynamic_context_switching {
)], )],
); );
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!(null)); assert_eq!(result, graphql_value!(null));
} }
@ -593,13 +593,13 @@ mod dynamic_context_switching {
( (
0, 0,
InnerContext { InnerContext {
value: "First value".to_owned(), value: "First value".into(),
}, },
), ),
( (
1, 1,
InnerContext { InnerContext {
value: "Second value".to_owned(), value: "Second value".into(),
}, },
), ),
] ]
@ -620,7 +620,7 @@ mod dynamic_context_switching {
)], )],
); );
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -647,13 +647,13 @@ mod dynamic_context_switching {
( (
0, 0,
InnerContext { InnerContext {
value: "First value".to_owned(), value: "First value".into(),
}, },
), ),
( (
1, 1,
InnerContext { InnerContext {
value: "Second value".to_owned(), value: "Second value".into(),
}, },
), ),
] ]
@ -667,7 +667,7 @@ mod dynamic_context_switching {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); assert_eq!(result, graphql_value!({"first": {"value": "First value"}}));
} }
@ -752,7 +752,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -783,7 +783,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!(null)); assert_eq!(result, graphql_value!(null));
@ -811,7 +811,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!(null)); assert_eq!(result, graphql_value!(null));
@ -839,7 +839,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!({"inner": {"nullableField": null}}),); assert_eq!(result, graphql_value!({"inner": {"nullableField": null}}),);
@ -867,7 +867,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!(null)); assert_eq!(result, graphql_value!(null));
@ -895,7 +895,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -926,7 +926,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!(result, graphql_value!(null)); assert_eq!(result, graphql_value!(null));
@ -954,7 +954,7 @@ mod propagates_errors_to_nullable_fields {
.await .await
.expect("Execution failed"); .expect("Execution failed");
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,

View file

@ -42,12 +42,12 @@ mod interface {
Schema { Schema {
pets: vec![ pets: vec![
Dog { Dog {
name: "Odie".to_owned(), name: "Odie".into(),
woofs: true, woofs: true,
} }
.into(), .into(),
Cat { Cat {
name: "Garfield".to_owned(), name: "Garfield".into(),
meows: false, meows: false,
} }
.into(), .into(),
@ -77,7 +77,7 @@ mod interface {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -170,11 +170,11 @@ mod union {
Schema { Schema {
pets: vec![ pets: vec![
Box::new(Dog { Box::new(Dog {
name: "Odie".to_owned(), name: "Odie".into(),
woofs: true, woofs: true,
}), }),
Box::new(Cat { Box::new(Cat {
name: "Garfield".to_owned(), name: "Garfield".into(),
meows: false, meows: false,
}), }),
], ],
@ -205,7 +205,7 @@ mod union {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,

View file

@ -92,7 +92,7 @@ where
F: Fn((&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>)) -> (), F: Fn((&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>)) -> (),
{ {
let schema = RootNode::new( let schema = RootNode::new(
Root {}, Root,
EmptyMutation::<()>::new(), EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(), EmptySubscription::<()>::new(),
); );
@ -103,7 +103,7 @@ where
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()

View file

@ -76,9 +76,9 @@ struct FieldDescription {
#[derive(GraphQLInputObject, Debug)] #[derive(GraphQLInputObject, Debug)]
struct FieldWithDefaults { struct FieldWithDefaults {
#[graphql(default = "123")] #[graphql(default = 123)]
field_one: i32, field_one: i32,
#[graphql(default = "456", description = "The second field")] #[graphql(default = 456, description = "The second field")]
field_two: i32, field_two: i32,
} }
@ -117,7 +117,7 @@ where
F: Fn(&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>) -> (), F: Fn(&Object<DefaultScalarValue>, &Vec<Value<DefaultScalarValue>>) -> (),
{ {
let schema = RootNode::new( let schema = RootNode::new(
Root {}, Root,
EmptyMutation::<()>::new(), EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(), EmptySubscription::<()>::new(),
); );
@ -128,7 +128,7 @@ where
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()
@ -312,7 +312,7 @@ fn derive_derived() {
format!( format!(
"{:?}", "{:?}",
Derive { Derive {
field_one: "test".to_owned(), field_one: "test".into(),
}, },
), ),
"Derive { field_one: \"test\" }" "Derive { field_one: \"test\" }"
@ -462,6 +462,9 @@ async fn field_with_defaults_introspection() {
name name
type { type {
name name
ofType {
name
}
} }
defaultValue defaultValue
} }
@ -477,12 +480,12 @@ async fn field_with_defaults_introspection() {
assert_eq!(fields.len(), 2); assert_eq!(fields.len(), 2);
assert!(fields.contains(&graphql_value!({ assert!(fields.contains(&graphql_value!({
"name": "fieldOne", "name": "fieldOne",
"type": {"name": "Int"}, "type": {"name": null, "ofType": {"name": "Int"}},
"defaultValue": "123", "defaultValue": "123",
}))); })));
assert!(fields.contains(&graphql_value!({ assert!(fields.contains(&graphql_value!({
"name": "fieldTwo", "name": "fieldTwo",
"type": {"name": "Int"}, "type": {"name": null, "ofType": {"name": "Int"}},
"defaultValue": "456", "defaultValue": "456",
}))); })));
}) })

View file

@ -69,7 +69,7 @@ async fn test_execution() {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
assert_eq!( assert_eq!(
result, result,
@ -114,7 +114,7 @@ async fn enum_introspection() {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()
@ -223,7 +223,7 @@ async fn interface_introspection() {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()
@ -247,7 +247,7 @@ async fn interface_introspection() {
); );
assert_eq!( assert_eq!(
type_info.get_field_value("interfaces"), type_info.get_field_value("interfaces"),
Some(&graphql_value!(null)), Some(&graphql_value!([])),
); );
assert_eq!( assert_eq!(
type_info.get_field_value("enumValues"), type_info.get_field_value("enumValues"),
@ -355,7 +355,7 @@ async fn object_introspection() {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()
@ -406,7 +406,7 @@ async fn object_introspection() {
assert_eq!(fields.len(), 2); assert_eq!(fields.len(), 2);
println!("Fields: {:#?}", fields); println!("Fields: {fields:#?}");
assert!(fields.contains(&graphql_value!({ assert!(fields.contains(&graphql_value!({
"name": "sampleEnum", "name": "sampleEnum",
@ -444,9 +444,13 @@ async fn object_introspection() {
"name": "second", "name": "second",
"description": "The second number", "description": "The second number",
"type": { "type": {
"name": "Int", "name": null,
"kind": "SCALAR", "kind": "NON_NULL",
"ofType": null, "ofType": {
"name": "Int",
"kind": "SCALAR",
"ofType": null,
},
}, },
"defaultValue": "123", "defaultValue": "123",
}], }],
@ -493,7 +497,7 @@ async fn scalar_introspection() {
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:#?}", result); println!("Result: {result:#?}");
let type_info = result let type_info = result
.as_object_value() .as_object_value()

View file

@ -23,7 +23,7 @@ impl TestComplexScalar {
v.as_string_value() v.as_string_value()
.filter(|s| *s == "SerializedValue") .filter(|s| *s == "SerializedValue")
.map(|_| Self) .map(|_| Self)
.ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {v}"#))
} }
} }
@ -49,7 +49,7 @@ struct ExampleInputObject {
#[derive(GraphQLInputObject, Debug)] #[derive(GraphQLInputObject, Debug)]
struct InputWithDefaults { struct InputWithDefaults {
#[graphql(default = "123")] #[graphql(default = 123)]
a: i32, a: i32,
} }
@ -58,41 +58,47 @@ struct TestType;
#[graphql_object] #[graphql_object]
impl TestType { impl TestType {
fn field_with_object_input(input: Option<TestInputObject>) -> String { fn field_with_object_input(input: Option<TestInputObject>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn field_with_nullable_string_input(input: Option<String>) -> String { fn field_with_nullable_string_input(input: Option<String>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn field_with_non_nullable_string_input(input: String) -> String { fn field_with_non_nullable_string_input(input: String) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn field_with_default_argument_value( fn field_with_default_argument_value(
#[graphql(default = "Hello World")] input: String, #[graphql(default = "Hello World")] input: String,
) -> String { ) -> String {
format!("{:?}", input) format!("{input:?}")
}
fn nullable_field_with_default_argument_value(
#[graphql(default = "Hello World".to_owned())] input: Option<String>,
) -> String {
format!("{input:?}")
} }
fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String { fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn list(input: Option<Vec<Option<String>>>) -> String { fn list(input: Option<Vec<Option<String>>>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn nn_list(input: Vec<Option<String>>) -> String { fn nn_list(input: Vec<Option<String>>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn list_nn(input: Option<Vec<String>>) -> String { fn list_nn(input: Option<Vec<String>>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn nn_list_nn(input: Vec<String>) -> String { fn nn_list_nn(input: Vec<String>) -> String {
format!("{:?}", input) format!("{input:?}")
} }
fn example_input(arg: ExampleInputObject) -> String { fn example_input(arg: ExampleInputObject) -> String {
@ -104,11 +110,11 @@ impl TestType {
} }
fn integer_input(value: i32) -> String { fn integer_input(value: i32) -> String {
format!("value: {}", value) format!("value: {value}")
} }
fn float_input(value: f64) -> String { fn float_input(value: f64) -> String {
format!("value: {}", value) format!("value: {value}")
} }
} }
@ -128,7 +134,7 @@ where
assert_eq!(errs, []); assert_eq!(errs, []);
println!("Result: {:?}", result); println!("Result: {result:?}");
let obj = result.as_object_value().expect("Result is not an object"); let obj = result.as_object_value().expect("Result is not an object");
@ -791,13 +797,14 @@ async fn default_argument_when_not_provided() {
} }
#[tokio::test] #[tokio::test]
async fn default_argument_when_nullable_variable_not_provided() { async fn provided_variable_overwrites_default_value() {
run_query( run_variable_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, r#"query q($input: String!) { fieldWithDefaultArgumentValue(input: $input) }"#,
graphql_vars! {"input": "Overwritten"},
|result| { |result| {
assert_eq!( assert_eq!(
result.get_field_value("fieldWithDefaultArgumentValue"), result.get_field_value("fieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#""Hello World""#)), Some(&graphql_value!(r#""Overwritten""#)),
); );
}, },
) )
@ -805,14 +812,28 @@ async fn default_argument_when_nullable_variable_not_provided() {
} }
#[tokio::test] #[tokio::test]
async fn default_argument_when_nullable_variable_set_to_null() { async fn default_argument_when_nullable_variable_not_provided() {
run_query(
r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#,
|result| {
assert_eq!(
result.get_field_value("nullableFieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#"Some("Hello World")"#)),
);
},
)
.await;
}
#[tokio::test]
async fn null_when_nullable_variable_of_argument_with_default_value_set_to_null() {
run_variable_query( run_variable_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#,
graphql_vars! {"input": null}, graphql_vars! {"input": null},
|result| { |result| {
assert_eq!( assert_eq!(
result.get_field_value("fieldWithDefaultArgumentValue"), result.get_field_value("nullableFieldWithDefaultArgumentValue"),
Some(&graphql_value!(r#""Hello World""#)), Some(&graphql_value!(r#"None"#)),
); );
}, },
) )

View file

@ -60,11 +60,8 @@ where
self.variables self.variables
.as_ref() .as_ref()
.and_then(|iv| { .and_then(|iv| {
iv.to_object_value().map(|o| { iv.to_object_value()
o.into_iter() .map(|o| o.into_iter().map(|(k, v)| (k.into(), v.clone())).collect())
.map(|(k, v)| (k.to_owned(), v.clone()))
.collect()
})
}) })
.unwrap_or_default() .unwrap_or_default()
} }
@ -86,11 +83,11 @@ where
/// ///
/// This is a simple wrapper around the `execute_sync` function exposed at the /// This is a simple wrapper around the `execute_sync` function exposed at the
/// top level of this crate. /// top level of this crate.
pub fn execute_sync<'a, QueryT, MutationT, SubscriptionT>( pub fn execute_sync<QueryT, MutationT, SubscriptionT>(
&'a self, &self,
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>, root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> GraphQLResponse<'a, S> ) -> GraphQLResponse<S>
where where
S: ScalarValue, S: ScalarValue,
QueryT: GraphQLType<S>, QueryT: GraphQLType<S>,
@ -114,7 +111,7 @@ where
&'a self, &'a self,
root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
context: &'a QueryT::Context, context: &'a QueryT::Context,
) -> GraphQLResponse<'a, S> ) -> GraphQLResponse<S>
where where
QueryT: GraphQLTypeAsync<S>, QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync, QueryT::TypeInfo: Sync,
@ -140,7 +137,7 @@ pub async fn resolve_into_stream<'req, 'rn, 'ctx, 'a, QueryT, MutationT, Subscri
req: &'req GraphQLRequest<S>, req: &'req GraphQLRequest<S>,
root_node: &'rn RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &'rn RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
context: &'ctx QueryT::Context, context: &'ctx QueryT::Context,
) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError>
where where
'req: 'a, 'req: 'a,
'rn: 'a, 'rn: 'a,
@ -166,16 +163,16 @@ where
/// to JSON and send it over the wire. Use the `is_ok` method to determine /// to JSON and send it over the wire. Use the `is_ok` method to determine
/// whether to send a 200 or 400 HTTP status code. /// whether to send a 200 or 400 HTTP status code.
#[derive(Debug)] #[derive(Debug)]
pub struct GraphQLResponse<'a, S = DefaultScalarValue>( pub struct GraphQLResponse<S = DefaultScalarValue>(
Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>>, Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>,
); );
impl<'a, S> GraphQLResponse<'a, S> impl<S> GraphQLResponse<S>
where where
S: ScalarValue, S: ScalarValue,
{ {
/// Constructs new `GraphQLResponse` using the given result /// Constructs new `GraphQLResponse` using the given result
pub fn from_result(r: Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>>) -> Self { pub fn from_result(r: Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>) -> Self {
Self(r) Self(r)
} }
@ -193,12 +190,11 @@ where
} }
} }
impl<'a, T> Serialize for GraphQLResponse<'a, T> impl<T> Serialize for GraphQLResponse<T>
where where
T: Serialize + ScalarValue, T: Serialize + ScalarValue,
Value<T>: Serialize, Value<T>: Serialize,
ExecutionError<T>: Serialize, ExecutionError<T>: Serialize,
GraphQLError<'a>: Serialize,
{ {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
@ -272,7 +268,7 @@ where
&'a self, &'a self,
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> GraphQLBatchResponse<'a, S> ) -> GraphQLBatchResponse<S>
where where
QueryT: GraphQLType<S>, QueryT: GraphQLType<S>,
MutationT: GraphQLType<S, Context = QueryT::Context>, MutationT: GraphQLType<S, Context = QueryT::Context>,
@ -298,7 +294,7 @@ where
&'a self, &'a self,
root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
context: &'a QueryT::Context, context: &'a QueryT::Context,
) -> GraphQLBatchResponse<'a, S> ) -> GraphQLBatchResponse<S>
where where
QueryT: GraphQLTypeAsync<S>, QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync, QueryT::TypeInfo: Sync,
@ -340,20 +336,17 @@ where
/// wheter to send a 200 or 400 HTTP status code. /// wheter to send a 200 or 400 HTTP status code.
#[derive(Serialize)] #[derive(Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum GraphQLBatchResponse<'a, S = DefaultScalarValue> pub enum GraphQLBatchResponse<S = DefaultScalarValue>
where where
S: ScalarValue, S: ScalarValue,
{ {
/// Result of a single operation in a GraphQL request. /// Result of a single operation in a GraphQL request.
Single(GraphQLResponse<'a, S>), Single(GraphQLResponse<S>),
/// Result of a batch operation in a GraphQL request. /// Result of a batch operation in a GraphQL request.
Batch(Vec<GraphQLResponse<'a, S>>), Batch(Vec<GraphQLResponse<S>>),
} }
impl<'a, S> GraphQLBatchResponse<'a, S> impl<S: ScalarValue> GraphQLBatchResponse<S> {
where
S: ScalarValue,
{
/// Returns if all the GraphQLResponse in this operation are ok, /// Returns if all the GraphQLResponse in this operation are ok,
/// you can use it to determine wheter to send a 200 or 400 HTTP status code. /// you can use it to determine wheter to send a 200 or 400 HTTP status code.
pub fn is_ok(&self) -> bool { pub fn is_ok(&self) -> bool {
@ -639,20 +632,20 @@ pub mod tests {
"type":"connection_init", "type":"connection_init",
"payload":{} "payload":{}
}"# }"#
.to_owned(), .into(),
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
"type":"connection_ack" "type":"connection_ack"
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT,
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
"type":"ka" "type":"ka"
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT,
), ),
WsIntegrationMessage::Send( WsIntegrationMessage::Send(
@ -666,7 +659,7 @@ pub mod tests {
"query":"subscription { asyncHuman { id, name, homePlanet } }" "query":"subscription { asyncHuman { id, name, homePlanet } }"
} }
}"# }"#
.to_owned(), .into(),
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
@ -682,7 +675,7 @@ pub mod tests {
} }
} }
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT,
), ),
]; ];
@ -692,7 +685,7 @@ pub mod tests {
async fn test_ws_invalid_json<T: WsIntegration>(integration: &T) { async fn test_ws_invalid_json<T: WsIntegration>(integration: &T) {
let messages = vec![ let messages = vec![
WsIntegrationMessage::Send("invalid json".to_owned()), WsIntegrationMessage::Send("invalid json".into()),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
"type":"connection_error", "type":"connection_error",
@ -700,7 +693,7 @@ pub mod tests {
"message":"serde error: expected value at line 1 column 1" "message":"serde error: expected value at line 1 column 1"
} }
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT,
), ),
]; ];
@ -715,20 +708,20 @@ pub mod tests {
"type":"connection_init", "type":"connection_init",
"payload":{} "payload":{}
}"# }"#
.to_owned(), .into(),
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
"type":"connection_ack" "type":"connection_ack"
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
"type":"ka" "type":"ka"
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT
), ),
WsIntegrationMessage::Send( WsIntegrationMessage::Send(
@ -742,7 +735,7 @@ pub mod tests {
"query":"subscription { asyncHuman }" "query":"subscription { asyncHuman }"
} }
}"# }"#
.to_owned(), .into(),
), ),
WsIntegrationMessage::Expect( WsIntegrationMessage::Expect(
r#"{ r#"{
@ -756,7 +749,7 @@ pub mod tests {
}] }]
}] }]
}"# }"#
.to_owned(), .into(),
WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT
) )
]; ];

View file

@ -32,8 +32,6 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value};
type BigDecimal = bigdecimal::BigDecimal; type BigDecimal = bigdecimal::BigDecimal;
mod bigdecimal_scalar { mod bigdecimal_scalar {
use std::convert::TryFrom as _;
use super::*; use super::*;
pub(super) fn to_output<S: ScalarValue>(v: &BigDecimal) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &BigDecimal) -> Value<S> {
@ -45,13 +43,13 @@ mod bigdecimal_scalar {
Ok(BigDecimal::from(i)) Ok(BigDecimal::from(i))
} else if let Some(f) = v.as_float_value() { } else if let Some(f) = v.as_float_value() {
BigDecimal::try_from(f) BigDecimal::try_from(f)
.map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {}", e)) .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}"))
} else { } else {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
BigDecimal::from_str(s) BigDecimal::from_str(s)
.map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {}", e)) .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {e}"))
}) })
} }
} }
@ -88,11 +86,10 @@ mod test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{:?}`: {:?}", "failed to parse `{input:?}`: {:?}",
input,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); assert_eq!(parsed.unwrap(), expected, "input: {input:?}");
} }
} }
@ -110,7 +107,7 @@ mod test {
let input: InputValue = input; let input: InputValue = input;
let parsed = BigDecimal::from_input_value(&input); let parsed = BigDecimal::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -126,7 +123,7 @@ mod test {
] { ] {
let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value();
assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}");
} }
} }
} }

View file

@ -14,9 +14,9 @@ mod object_id {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<ObjectId, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<ObjectId, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e)) ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {e}"))
}) })
} }
} }
@ -28,15 +28,18 @@ mod utc_date_time {
use super::*; use super::*;
pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> {
Value::scalar((*v).to_rfc3339_string()) Value::scalar(
(*v).try_to_rfc3339_string()
.unwrap_or_else(|e| panic!("failed to format `UtcDateTime` as RFC3339: {e}")),
)
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
UtcDateTime::parse_rfc3339_str(s) UtcDateTime::parse_rfc3339_str(s)
.map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e)) .map_err(|e| format!("Failed to parse `UtcDateTime`: {e}"))
}) })
} }
} }

View file

@ -62,9 +62,9 @@ mod date {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)) Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}"))
}) })
} }
} }
@ -127,7 +127,7 @@ mod local_time {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
// First, try to parse the most used format. // First, try to parse the most used format.
// At the end, try to parse the full format for the parsing // At the end, try to parse the full format for the parsing
@ -135,7 +135,7 @@ mod local_time {
LocalTime::parse_from_str(s, FORMAT_NO_MILLIS) LocalTime::parse_from_str(s, FORMAT_NO_MILLIS)
.or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS))
.or_else(|_| LocalTime::parse_from_str(s, FORMAT)) .or_else(|_| LocalTime::parse_from_str(s, FORMAT))
.map_err(|e| format!("Invalid `LocalTime`: {}", e)) .map_err(|e| format!("Invalid `LocalTime`: {e}"))
}) })
} }
} }
@ -166,10 +166,10 @@ mod local_date_time {
S: ScalarValue, S: ScalarValue,
{ {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
LocalDateTime::parse_from_str(s, FORMAT) LocalDateTime::parse_from_str(s, FORMAT)
.map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) .map_err(|e| format!("Invalid `LocalDateTime`: {e}"))
}) })
} }
} }
@ -219,10 +219,10 @@ mod date_time {
Tz: TimeZone + FromFixedOffset, Tz: TimeZone + FromFixedOffset,
{ {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
DateTime::<FixedOffset>::parse_from_rfc3339(s) DateTime::<FixedOffset>::parse_from_rfc3339(s)
.map_err(|e| format!("Invalid `DateTime`: {}", e)) .map_err(|e| format!("Invalid `DateTime`: {e}"))
.map(FromFixedOffset::from_fixed_offset) .map(FromFixedOffset::from_fixed_offset)
}) })
} }
@ -345,11 +345,10 @@ mod date_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -372,7 +371,7 @@ mod date_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = Date::from_input_value(&input); let parsed = Date::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -394,7 +393,7 @@ mod date_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -420,11 +419,10 @@ mod local_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -451,7 +449,7 @@ mod local_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = LocalTime::from_input_value(&input); let parsed = LocalTime::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -477,7 +475,7 @@ mod local_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -513,11 +511,10 @@ mod local_date_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -546,7 +543,7 @@ mod local_date_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = LocalDateTime::from_input_value(&input); let parsed = LocalDateTime::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -570,7 +567,7 @@ mod local_date_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -635,11 +632,10 @@ mod date_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -673,7 +669,7 @@ mod date_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = DateTime::<FixedOffset>::from_input_value(&input); let parsed = DateTime::<FixedOffset>::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -703,7 +699,7 @@ mod date_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }

View file

@ -34,10 +34,10 @@ mod tz {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<TimeZone, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<TimeZone, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
s.parse::<TimeZone>() s.parse::<TimeZone>()
.map_err(|e| format!("Failed to parse `TimeZone`: {}", e)) .map_err(|e| format!("Failed to parse `TimeZone`: {e}"))
}) })
} }
} }

View file

@ -34,8 +34,6 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value};
type Decimal = rust_decimal::Decimal; type Decimal = rust_decimal::Decimal;
mod rust_decimal_scalar { mod rust_decimal_scalar {
use std::convert::TryFrom as _;
use super::*; use super::*;
pub(super) fn to_output<S: ScalarValue>(v: &Decimal) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &Decimal) -> Value<S> {
@ -46,14 +44,13 @@ mod rust_decimal_scalar {
if let Some(i) = v.as_int_value() { if let Some(i) = v.as_int_value() {
Ok(Decimal::from(i)) Ok(Decimal::from(i))
} else if let Some(f) = v.as_float_value() { } else if let Some(f) = v.as_float_value() {
Decimal::try_from(f) Decimal::try_from(f).map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}"))
.map_err(|e| format!("Failed to parse `Decimal` from `Float`: {}", e))
} else { } else {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
Decimal::from_str(s) Decimal::from_str(s)
.map_err(|e| format!("Failed to parse `Decimal` from `String`: {}", e)) .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}"))
}) })
} }
} }
@ -84,11 +81,10 @@ mod test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{:?}`: {:?}", "failed to parse `{input:?}`: {:?}",
input,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); assert_eq!(parsed.unwrap(), expected, "input: {input:?}");
} }
} }
@ -108,7 +104,7 @@ mod test {
let input: InputValue = input; let input: InputValue = input;
let parsed = Decimal::from_input_value(&input); let parsed = Decimal::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -117,7 +113,7 @@ mod test {
for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] {
let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value();
assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}");
} }
} }
} }

View file

@ -1,8 +1,4 @@
use std::{ use std::{fmt, marker::PhantomData};
convert::{TryFrom as _, TryInto as _},
fmt,
marker::PhantomData,
};
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::{ use serde::{
@ -42,7 +38,7 @@ impl<T: Serialize> Serialize for ExecutionError<T> {
} }
} }
impl<'a> Serialize for GraphQLError<'a> { impl Serialize for GraphQLError {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)] #[derive(Serialize)]
struct Helper { struct Helper {
@ -247,11 +243,11 @@ impl Serialize for SourcePosition {
} }
} }
impl<'a> Serialize for Spanning<ParseError<'a>> { impl Serialize for Spanning<ParseError> {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
let mut map = ser.serialize_map(Some(2))?; let mut map = ser.serialize_map(Some(2))?;
let msg = format!("{}", self.item); let msg = self.item.to_string();
map.serialize_key("message")?; map.serialize_key("message")?;
map.serialize_value(&msg)?; map.serialize_value(&msg)?;
@ -396,7 +392,7 @@ mod tests {
#[test] #[test]
fn error_extensions() { fn error_extensions() {
let mut obj: Object<DefaultScalarValue> = Object::with_capacity(1); let mut obj: Object<DefaultScalarValue> = Object::with_capacity(1);
obj.add_field("foo".to_string(), Value::scalar("bar")); obj.add_field("foo", Value::scalar("bar"));
assert_eq!( assert_eq!(
to_string(&ExecutionError::at_origin(FieldError::new( to_string(&ExecutionError::at_origin(FieldError::new(
"foo error", "foo error",

View file

@ -57,14 +57,14 @@ mod date {
pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> {
Value::scalar( Value::scalar(
v.format(FORMAT) v.format(FORMAT)
.unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), .unwrap_or_else(|e| panic!("Failed to format `Date`: {e}")),
) )
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Date, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Date, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) .and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}")))
} }
} }
@ -109,13 +109,13 @@ mod local_time {
} else { } else {
v.format(FORMAT) v.format(FORMAT)
} }
.unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)), .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {e}")),
) )
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalTime, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalTime, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
// First, try to parse the most used format. // First, try to parse the most used format.
// At the end, try to parse the full format for the parsing // At the end, try to parse the full format for the parsing
@ -123,7 +123,7 @@ mod local_time {
LocalTime::parse(s, FORMAT_NO_MILLIS) LocalTime::parse(s, FORMAT_NO_MILLIS)
.or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS))
.or_else(|_| LocalTime::parse(s, FORMAT)) .or_else(|_| LocalTime::parse(s, FORMAT))
.map_err(|e| format!("Invalid `LocalTime`: {}", e)) .map_err(|e| format!("Invalid `LocalTime`: {e}"))
}) })
} }
} }
@ -146,16 +146,15 @@ mod local_date_time {
pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> {
Value::scalar( Value::scalar(
v.format(FORMAT) v.format(FORMAT)
.unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)), .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {e}")),
) )
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalDateTime, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalDateTime, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
LocalDateTime::parse(s, FORMAT) LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}"))
.map_err(|e| format!("Invalid `LocalDateTime`: {}", e))
}) })
} }
} }
@ -186,15 +185,15 @@ mod date_time {
Value::scalar( Value::scalar(
v.to_offset(UtcOffset::UTC) v.to_offset(UtcOffset::UTC)
.format(&Rfc3339) .format(&Rfc3339)
.unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)), .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {e}")),
) )
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTime, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTime, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e)) DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {e}"))
}) })
.map(|dt| dt.to_offset(UtcOffset::UTC)) .map(|dt| dt.to_offset(UtcOffset::UTC))
} }
@ -228,16 +227,16 @@ mod utc_offset {
pub(super) fn to_output<S: ScalarValue>(v: &UtcOffset) -> Value<S> { pub(super) fn to_output<S: ScalarValue>(v: &UtcOffset) -> Value<S> {
Value::scalar( Value::scalar(
v.format(UTC_OFFSET_FORMAT) v.format(UTC_OFFSET_FORMAT)
.unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)), .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {e}")),
) )
} }
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcOffset, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcOffset, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| { .and_then(|s| {
UtcOffset::parse(s, UTC_OFFSET_FORMAT) UtcOffset::parse(s, UTC_OFFSET_FORMAT)
.map_err(|e| format!("Invalid `UtcOffset`: {}", e)) .map_err(|e| format!("Invalid `UtcOffset`: {e}"))
}) })
} }
} }
@ -261,11 +260,10 @@ mod date_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -288,7 +286,7 @@ mod date_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = Date::from_input_value(&input); let parsed = Date::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -302,7 +300,7 @@ mod date_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -330,11 +328,10 @@ mod local_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -364,7 +361,7 @@ mod local_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = LocalTime::from_input_value(&input); let parsed = LocalTime::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -378,7 +375,7 @@ mod local_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -402,11 +399,10 @@ mod local_date_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -437,7 +433,7 @@ mod local_date_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = LocalDateTime::from_input_value(&input); let parsed = LocalDateTime::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -455,7 +451,7 @@ mod local_date_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -490,11 +486,10 @@ mod date_time_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -528,7 +523,7 @@ mod date_time_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = DateTime::from_input_value(&input); let parsed = DateTime::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -546,7 +541,7 @@ mod date_time_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }
@ -574,11 +569,10 @@ mod utc_offset_test {
assert!( assert!(
parsed.is_ok(), parsed.is_ok(),
"failed to parse `{}`: {:?}", "failed to parse `{raw}`: {:?}",
raw,
parsed.unwrap_err(), parsed.unwrap_err(),
); );
assert_eq!(parsed.unwrap(), expected, "input: {}", raw); assert_eq!(parsed.unwrap(), expected, "input: {raw}");
} }
} }
@ -607,7 +601,7 @@ mod utc_offset_test {
let input: InputValue = input; let input: InputValue = input;
let parsed = UtcOffset::from_input_value(&input); let parsed = UtcOffset::from_input_value(&input);
assert!(parsed.is_err(), "allows input: {:?}", input); assert!(parsed.is_err(), "allows input: {input:?}");
} }
} }
@ -620,7 +614,7 @@ mod utc_offset_test {
] { ] {
let actual: InputValue = val.to_input_value(); let actual: InputValue = val.to_input_value();
assert_eq!(actual, expected, "on value: {}", val); assert_eq!(actual, expected, "on value: {val}");
} }
} }
} }

View file

@ -14,8 +14,8 @@ mod url_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Url, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Url, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {e}")))
} }
} }

View file

@ -16,8 +16,8 @@ mod uuid_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Uuid, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Uuid, String> {
v.as_string_value() v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {e}")))
} }
} }

View file

@ -5,15 +5,12 @@ pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str =
/// The desired GraphQL introspection format for the canonical query /// The desired GraphQL introspection format for the canonical query
/// (<https://github.com/graphql/graphql-js/blob/90bd6ff72625173dd39a1f82cfad9336cfad8f65/src/utilities/getIntrospectionQuery.ts#L62>) /// (<https://github.com/graphql/graphql-js/blob/90bd6ff72625173dd39a1f82cfad9336cfad8f65/src/utilities/getIntrospectionQuery.ts#L62>)
#[derive(Clone, Copy, Debug, Default)]
pub enum IntrospectionFormat { pub enum IntrospectionFormat {
/// The canonical GraphQL introspection query. /// The canonical GraphQL introspection query.
#[default]
All, All,
/// The canonical GraphQL introspection query without descriptions. /// The canonical GraphQL introspection query without descriptions.
WithoutDescriptions, WithoutDescriptions,
} }
impl Default for IntrospectionFormat {
fn default() -> Self {
IntrospectionFormat::All
}
}

View file

@ -6,6 +6,7 @@
// Required for using `juniper_codegen` macros inside this crate to resolve // Required for using `juniper_codegen` macros inside this crate to resolve
// absolute `::juniper` path correctly, without errors. // absolute `::juniper` path correctly, without errors.
extern crate core;
extern crate self as juniper; extern crate self as juniper;
use std::fmt; use std::fmt;
@ -93,10 +94,10 @@ pub use crate::{
}; };
/// An error that prevented query execution /// An error that prevented query execution
#[derive(Debug, PartialEq)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum GraphQLError<'a> { #[derive(Debug, Eq, PartialEq)]
ParseError(Spanning<ParseError<'a>>), pub enum GraphQLError {
ParseError(Spanning<ParseError>),
ValidationError(Vec<RuleError>), ValidationError(Vec<RuleError>),
NoOperationProvided, NoOperationProvided,
MultipleOperationsProvided, MultipleOperationsProvided,
@ -105,26 +106,38 @@ pub enum GraphQLError<'a> {
NotSubscription, NotSubscription,
} }
impl<'a> fmt::Display for GraphQLError<'a> { impl fmt::Display for GraphQLError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
GraphQLError::ParseError(error) => write!(f, "{}", error), Self::ParseError(e) => write!(f, "{e}"),
GraphQLError::ValidationError(errors) => { Self::ValidationError(errs) => {
for error in errors { for e in errs {
writeln!(f, "{}", error)?; writeln!(f, "{e}")?;
} }
Ok(()) Ok(())
} }
GraphQLError::NoOperationProvided => write!(f, "No operation provided"), Self::NoOperationProvided => write!(f, "No operation provided"),
GraphQLError::MultipleOperationsProvided => write!(f, "Multiple operations provided"), Self::MultipleOperationsProvided => write!(f, "Multiple operations provided"),
GraphQLError::UnknownOperationName => write!(f, "Unknown operation name"), Self::UnknownOperationName => write!(f, "Unknown operation name"),
GraphQLError::IsSubscription => write!(f, "Operation is a subscription"), Self::IsSubscription => write!(f, "Operation is a subscription"),
GraphQLError::NotSubscription => write!(f, "Operation is not a subscription"), Self::NotSubscription => write!(f, "Operation is not a subscription"),
} }
} }
} }
impl<'a> std::error::Error for GraphQLError<'a> {} impl std::error::Error for GraphQLError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ParseError(e) => Some(e),
Self::ValidationError(errs) => Some(errs.first()?),
Self::NoOperationProvided
| Self::MultipleOperationsProvided
| Self::UnknownOperationName
| Self::IsSubscription
| Self::NotSubscription => None,
}
}
}
/// Execute a query synchronously in a provided schema /// Execute a query synchronously in a provided schema
pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>( pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>(
@ -133,7 +146,7 @@ pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>(
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where where
S: ScalarValue, S: ScalarValue,
QueryT: GraphQLType<S>, QueryT: GraphQLType<S>,
@ -172,7 +185,7 @@ pub async fn execute<'a, S, QueryT, MutationT, SubscriptionT>(
root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &QueryT::Context, context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where where
QueryT: GraphQLTypeAsync<S>, QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync, QueryT::TypeInfo: Sync,
@ -216,7 +229,7 @@ pub async fn resolve_into_stream<'a, S, QueryT, MutationT, SubscriptionT>(
root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>, variables: &Variables<S>,
context: &'a QueryT::Context, context: &'a QueryT::Context,
) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError>
where where
QueryT: GraphQLTypeAsync<S>, QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync, QueryT::TypeInfo: Sync,
@ -259,7 +272,7 @@ pub fn introspect<'a, S, QueryT, MutationT, SubscriptionT>(
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>, root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
context: &QueryT::Context, context: &QueryT::Context,
format: IntrospectionFormat, format: IntrospectionFormat,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> ) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where where
S: ScalarValue, S: ScalarValue,
QueryT: GraphQLType<S>, QueryT: GraphQLType<S>,
@ -278,8 +291,8 @@ where
) )
} }
impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> { impl From<Spanning<ParseError>> for GraphQLError {
fn from(f: Spanning<ParseError<'a>>) -> GraphQLError<'a> { fn from(err: Spanning<ParseError>) -> Self {
GraphQLError::ParseError(f) Self::ParseError(err)
} }
} }

View file

@ -217,37 +217,35 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": 123}, graphql_vars! {"key": 123},
vec![("key".to_owned(), IV::scalar(123))] vec![("key".into(), IV::scalar(123))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": "val"}, graphql_vars! {"key": "val"},
vec![("key".to_owned(), IV::scalar("val"))] vec![("key".into(), IV::scalar("val"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": 1.23}, graphql_vars! {"key": 1.23},
vec![("key".to_owned(), IV::scalar(1.23))] vec![("key".into(), IV::scalar(1.23))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": 1 + 2}, graphql_vars! {"key": 1 + 2},
vec![("key".to_owned(), IV::scalar(3))] vec![("key".into(), IV::scalar(3))].into_iter().collect(),
.into_iter()
.collect(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": false}, graphql_vars! {"key": false},
vec![("key".to_owned(), IV::scalar(false))] vec![("key".into(), IV::scalar(false))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": (val)}, graphql_vars! {"key": (val)},
vec![("key".to_owned(), IV::scalar(42))] vec![("key".into(), IV::scalar(42))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
@ -257,13 +255,13 @@ mod tests {
fn r#enum() { fn r#enum() {
assert_eq!( assert_eq!(
graphql_vars! {"key": ENUM}, graphql_vars! {"key": ENUM},
vec![("key".to_owned(), IV::enum_value("ENUM"))] vec![("key".into(), IV::enum_value("ENUM"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": lowercase}, graphql_vars! {"key": lowercase},
vec![("key".to_owned(), IV::enum_value("lowercase"))] vec![("key".into(), IV::enum_value("lowercase"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
@ -273,19 +271,19 @@ mod tests {
fn variable() { fn variable() {
assert_eq!( assert_eq!(
graphql_vars! {"key": @var}, graphql_vars! {"key": @var},
vec![("key".to_owned(), IV::variable("var"))] vec![("key".into(), IV::variable("var"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": @array}, graphql_vars! {"key": @array},
vec![("key".to_owned(), IV::variable("array"))] vec![("key".into(), IV::variable("array"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": @object}, graphql_vars! {"key": @object},
vec![("key".to_owned(), IV::variable("object"))] vec![("key".into(), IV::variable("object"))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
@ -297,68 +295,65 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": []}, graphql_vars! {"key": []},
vec![("key".to_owned(), IV::list(vec![]))] vec![("key".into(), IV::list(vec![]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [null]}, graphql_vars! {"key": [null]},
vec![("key".to_owned(), IV::list(vec![IV::Null]))] vec![("key".into(), IV::list(vec![IV::Null]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1]}, graphql_vars! {"key": [1]},
vec![("key".to_owned(), IV::list(vec![IV::scalar(1)]))] vec![("key".into(), IV::list(vec![IV::scalar(1)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [1 + 2]}, graphql_vars! {"key": [1 + 2]},
vec![("key".to_owned(), IV::list(vec![IV::scalar(3)]))] vec![("key".into(), IV::list(vec![IV::scalar(3)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [(val)]}, graphql_vars! {"key": [(val)]},
vec![("key".to_owned(), IV::list(vec![IV::scalar(42)]))] vec![("key".into(), IV::list(vec![IV::scalar(42)]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [ENUM]}, graphql_vars! {"key": [ENUM]},
vec![("key".to_owned(), IV::list(vec![IV::enum_value("ENUM")]))] vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [lowercase]}, graphql_vars! {"key": [lowercase]},
vec![( vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))]
"key".to_owned(), .into_iter()
IV::list(vec![IV::enum_value("lowercase")]) .collect::<V>(),
)]
.into_iter()
.collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@var]}, graphql_vars! {"key": [@var]},
vec![("key".to_owned(), IV::list(vec![IV::variable("var")]))] vec![("key".into(), IV::list(vec![IV::variable("var")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@array]}, graphql_vars! {"key": [@array]},
vec![("key".to_owned(), IV::list(vec![IV::variable("array")]))] vec![("key".into(), IV::list(vec![IV::variable("array")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": [@object]}, graphql_vars! {"key": [@object]},
vec![("key".to_owned(), IV::list(vec![IV::variable("object")]))] vec![("key".into(), IV::list(vec![IV::variable("object")]))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
@ -366,7 +361,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [2], 3]}, graphql_vars! {"key": [1, [2], 3]},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::list(vec![ IV::list(vec![
IV::scalar(1), IV::scalar(1),
IV::list(vec![IV::scalar(2)]), IV::list(vec![IV::scalar(2)]),
@ -379,7 +374,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [2 + 3], 3]}, graphql_vars! {"key": [1, [2 + 3], 3]},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::list(vec![ IV::list(vec![
IV::scalar(1), IV::scalar(1),
IV::list(vec![IV::scalar(5)]), IV::list(vec![IV::scalar(5)]),
@ -392,7 +387,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [ENUM], (val)]}, graphql_vars! {"key": [1, [ENUM], (val)]},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::list(vec![ IV::list(vec![
IV::scalar(1), IV::scalar(1),
IV::list(vec![IV::enum_value("ENUM")]), IV::list(vec![IV::enum_value("ENUM")]),
@ -405,7 +400,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": [1 + 2, [(val)], @val]}, graphql_vars! {"key": [1 + 2, [(val)], @val]},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::list(vec![ IV::list(vec![
IV::scalar(3), IV::scalar(3),
IV::list(vec![IV::scalar(42)]), IV::list(vec![IV::scalar(42)]),
@ -418,7 +413,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": [1, [@val], ENUM]}, graphql_vars! {"key": [1, [@val], ENUM]},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::list(vec![ IV::list(vec![
IV::scalar(1), IV::scalar(1),
IV::list(vec![IV::variable("val")]), IV::list(vec![IV::variable("val")]),
@ -436,14 +431,14 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {}}, graphql_vars! {"key": {}},
vec![("key".to_owned(), IV::object(IndexMap::<String, _>::new()))] vec![("key".into(), IV::object(IndexMap::<String, _>::new()))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": null}}, graphql_vars! {"key": {"key": null}},
vec![("key".to_owned(), IV::object(indexmap! {"key" => IV::Null}))] vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),
); );
@ -451,7 +446,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": 123}}, graphql_vars! {"key": {"key": 123}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::scalar(123)}), IV::object(indexmap! {"key" => IV::scalar(123)}),
)] )]
.into_iter() .into_iter()
@ -459,17 +454,14 @@ mod tests {
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": 1 + 2}}, graphql_vars! {"key": {"key": 1 + 2}},
vec![( vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)]
"key".to_owned(), .into_iter()
IV::object(indexmap! {"key" => IV::scalar(3)}), .collect::<V>(),
)]
.into_iter()
.collect::<V>(),
); );
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": (val)}}, graphql_vars! {"key": {"key": (val)}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::scalar(42)}), IV::object(indexmap! {"key" => IV::scalar(42)}),
)] )]
.into_iter() .into_iter()
@ -479,7 +471,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": []}}, graphql_vars! {"key": {"key": []}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![])}), IV::object(indexmap! {"key" => IV::list(vec![])}),
)] )]
.into_iter() .into_iter()
@ -488,7 +480,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [null]}}, graphql_vars! {"key": {"key": [null]}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}), IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}),
)] )]
.into_iter() .into_iter()
@ -497,7 +489,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [1]}}, graphql_vars! {"key": {"key": [1]}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}),
)] )]
.into_iter() .into_iter()
@ -506,7 +498,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [1 + 2]}}, graphql_vars! {"key": {"key": [1 + 2]}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}),
)] )]
.into_iter() .into_iter()
@ -515,7 +507,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": [(val)]}}, graphql_vars! {"key": {"key": [(val)]}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}),
)] )]
.into_iter() .into_iter()
@ -524,7 +516,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": ENUM}}, graphql_vars! {"key": {"key": ENUM}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::enum_value("ENUM")}), IV::object(indexmap! {"key" => IV::enum_value("ENUM")}),
)] )]
.into_iter() .into_iter()
@ -533,7 +525,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": lowercase}}, graphql_vars! {"key": {"key": lowercase}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::enum_value("lowercase")}), IV::object(indexmap! {"key" => IV::enum_value("lowercase")}),
)] )]
.into_iter() .into_iter()
@ -542,7 +534,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": @val}}, graphql_vars! {"key": {"key": @val}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::variable("val")}), IV::object(indexmap! {"key" => IV::variable("val")}),
)] )]
.into_iter() .into_iter()
@ -551,7 +543,7 @@ mod tests {
assert_eq!( assert_eq!(
graphql_vars! {"key": {"key": @array}}, graphql_vars! {"key": {"key": @array}},
vec![( vec![(
"key".to_owned(), "key".into(),
IV::object(indexmap! {"key" => IV::variable("array")}), IV::object(indexmap! {"key" => IV::variable("array")}),
)] )]
.into_iter() .into_iter()
@ -576,7 +568,7 @@ mod tests {
}, },
vec![ vec![
( (
"inner".to_owned(), "inner".into(),
IV::object(indexmap! { IV::object(indexmap! {
"key1" => IV::scalar(42), "key1" => IV::scalar(42),
"key2" => IV::scalar("val"), "key2" => IV::scalar("val"),
@ -602,7 +594,7 @@ mod tests {
]), ]),
}), }),
), ),
("more".to_owned(), IV::variable("var")), ("more".into(), IV::variable("var")),
] ]
.into_iter() .into_iter()
.collect::<V>(), .collect::<V>(),

View file

@ -41,8 +41,7 @@ where
/// [`GraphQLType::name`]: crate::GraphQLType::name /// [`GraphQLType::name`]: crate::GraphQLType::name
pub fn err_unnamed_type<S>(name: &str) -> FieldError<S> { pub fn err_unnamed_type<S>(name: &str) -> FieldError<S> {
FieldError::from(format!( FieldError::from(format!(
"Expected `{}` type to implement `GraphQLType::name`", "Expected `{name}` type to implement `GraphQLType::name`",
name,
)) ))
} }

View file

@ -563,6 +563,38 @@ macro_rules! assert_interfaces_impls {
}; };
} }
/// Asserts that all [transitive interfaces][0] (the ones implemented by the
/// `$interface`) are also implemented by the `$implementor`.
///
/// [0]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P
#[macro_export]
macro_rules! assert_transitive_impls {
($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => {
const _: () = {
$({
let is_present = $crate::macros::reflect::str_exists_in_arr(
<$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME,
<$transitive as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
);
if !is_present {
const MSG: &str = $crate::const_concat!(
"Failed to implement interface `",
<$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`: missing `impl = ` for transitive interface `",
<$transitive as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`."
);
::std::panic!("{}", MSG);
}
})*
};
};
}
/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. /// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`].
/// ///
/// This assertion is a combination of [`assert_subtype`] and /// This assertion is a combination of [`assert_subtype`] and
@ -863,11 +895,11 @@ macro_rules! const_concat {
} }
const CON: [u8; LEN] = concat([$($s),*]); const CON: [u8; LEN] = concat([$($s),*]);
// TODO: Use `str::from_utf8()` once it becomes `const`. // TODO: Use `.unwrap()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one match ::std::str::from_utf8(&CON) {
// after another byte-by-byte. ::std::result::Result::Ok(s) => s,
#[allow(unsafe_code)] _ => unreachable!(),
unsafe { ::std::str::from_utf8_unchecked(&CON) } }
}}; }};
} }
@ -985,12 +1017,11 @@ macro_rules! format_type {
const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); const TYPE_ARR: [u8; RES_LEN] = format_type_arr();
// TODO: Use `str::from_utf8()` once it becomes `const`. // TODO: Use `.unwrap()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one const TYPE_FORMATTED: &str = match ::std::str::from_utf8(TYPE_ARR.as_slice()) {
// after another byte-by-byte. ::std::result::Result::Ok(s) => s,
#[allow(unsafe_code)] _ => unreachable!(),
const TYPE_FORMATTED: &str = };
unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) };
TYPE_FORMATTED TYPE_FORMATTED
}}; }};

View file

@ -22,7 +22,7 @@ use crate::{
pub fn parse_document_source<'a, 'b, S>( pub fn parse_document_source<'a, 'b, S>(
s: &'a str, s: &'a str,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> UnlocatedParseResult<'a, OwnedDocument<'a, S>> ) -> UnlocatedParseResult<OwnedDocument<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -34,7 +34,7 @@ where
fn parse_document<'a, 'b, S>( fn parse_document<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> UnlocatedParseResult<'a, OwnedDocument<'a, S>> ) -> UnlocatedParseResult<OwnedDocument<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -52,7 +52,7 @@ where
fn parse_definition<'a, 'b, S>( fn parse_definition<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> UnlocatedParseResult<'a, Definition<'a, S>> ) -> UnlocatedParseResult<Definition<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -66,14 +66,14 @@ where
Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition( Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition(
parser, schema, parser, schema,
)?)), )?)),
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
} }
} }
fn parse_operation_definition<'a, 'b, S>( fn parse_operation_definition<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> ParseResult<'a, Operation<'a, S>> ) -> ParseResult<Operation<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -129,7 +129,7 @@ where
fn parse_fragment_definition<'a, 'b, S>( fn parse_fragment_definition<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> ParseResult<'a, Fragment<'a, S>> ) -> ParseResult<Fragment<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -139,7 +139,7 @@ where
let name = match parser.expect_name() { let name = match parser.expect_name() {
Ok(n) => { Ok(n) => {
if n.item == "on" { if n.item == "on" {
return Err(n.map(|_| ParseError::UnexpectedToken(Token::Name("on")))); return Err(n.map(|_| ParseError::UnexpectedToken("on".into())));
} else { } else {
n n
} }
@ -174,7 +174,7 @@ fn parse_optional_selection_set<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
fields: Option<&[&MetaField<'b, S>]>, fields: Option<&[&MetaField<'b, S>]>,
) -> OptionParseResult<'a, Vec<Selection<'a, S>>> ) -> OptionParseResult<Vec<Selection<'a, S>>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -189,7 +189,7 @@ fn parse_selection_set<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
fields: Option<&[&MetaField<'b, S>]>, fields: Option<&[&MetaField<'b, S>]>,
) -> ParseResult<'a, Vec<Selection<'a, S>>> ) -> ParseResult<Vec<Selection<'a, S>>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -204,7 +204,7 @@ fn parse_selection<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
fields: Option<&[&MetaField<'b, S>]>, fields: Option<&[&MetaField<'b, S>]>,
) -> UnlocatedParseResult<'a, Selection<'a, S>> ) -> UnlocatedParseResult<Selection<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -218,7 +218,7 @@ fn parse_fragment<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
fields: Option<&[&MetaField<'b, S>]>, fields: Option<&[&MetaField<'b, S>]>,
) -> UnlocatedParseResult<'a, Selection<'a, S>> ) -> UnlocatedParseResult<Selection<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -292,7 +292,7 @@ where
}, },
))) )))
} }
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
} }
} }
@ -300,7 +300,7 @@ fn parse_field<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
fields: Option<&[&MetaField<'b, S>]>, fields: Option<&[&MetaField<'b, S>]>,
) -> ParseResult<'a, Field<'a, S>> ) -> ParseResult<Field<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -351,7 +351,7 @@ fn parse_arguments<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
arguments: Option<&[Argument<'b, S>]>, arguments: Option<&[Argument<'b, S>]>,
) -> OptionParseResult<'a, Arguments<'a, S>> ) -> OptionParseResult<Arguments<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -376,7 +376,7 @@ fn parse_argument<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
arguments: Option<&[Argument<'b, S>]>, arguments: Option<&[Argument<'b, S>]>,
) -> ParseResult<'a, (Spanning<&'a str>, Spanning<InputValue<S>>)> ) -> ParseResult<(Spanning<&'a str>, Spanning<InputValue<S>>)>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -395,21 +395,21 @@ where
)) ))
} }
fn parse_operation_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, OperationType> { fn parse_operation_type(parser: &mut Parser<'_>) -> ParseResult<OperationType> {
match parser.peek().item { match parser.peek().item {
Token::Name("query") => Ok(parser.next_token()?.map(|_| OperationType::Query)), Token::Name("query") => Ok(parser.next_token()?.map(|_| OperationType::Query)),
Token::Name("mutation") => Ok(parser.next_token()?.map(|_| OperationType::Mutation)), Token::Name("mutation") => Ok(parser.next_token()?.map(|_| OperationType::Mutation)),
Token::Name("subscription") => { Token::Name("subscription") => {
Ok(parser.next_token()?.map(|_| OperationType::Subscription)) Ok(parser.next_token()?.map(|_| OperationType::Subscription))
} }
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
} }
} }
fn parse_variable_definitions<'a, 'b, S>( fn parse_variable_definitions<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> OptionParseResult<'a, VariableDefinitions<'a, S>> ) -> OptionParseResult<VariableDefinitions<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -433,7 +433,7 @@ where
fn parse_variable_definition<'a, 'b, S>( fn parse_variable_definition<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> ParseResult<'a, (Spanning<&'a str>, VariableDefinition<'a, S>)> ) -> ParseResult<(Spanning<&'a str>, VariableDefinition<'a, S>)>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -473,7 +473,7 @@ where
fn parse_directives<'a, 'b, S>( fn parse_directives<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> OptionParseResult<'a, Vec<Spanning<Directive<'a, S>>>> ) -> OptionParseResult<Vec<Spanning<Directive<'a, S>>>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -492,7 +492,7 @@ where
fn parse_directive<'a, 'b, S>( fn parse_directive<'a, 'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'a>,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> ParseResult<'a, Directive<'a, S>> ) -> ParseResult<Directive<'a, S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -516,7 +516,7 @@ where
)) ))
} }
pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Type<'a>> { pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<Type<'a>> {
let parsed_type = if let Some(Spanning { let parsed_type = if let Some(Spanning {
start: start_pos, .. start: start_pos, ..
}) = parser.skip(&Token::BracketOpen)? }) = parser.skip(&Token::BracketOpen)?
@ -541,10 +541,7 @@ pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Type<'a>> {
}) })
} }
fn wrap_non_null<'a>( fn wrap_non_null<'a>(parser: &mut Parser<'a>, inner: Spanning<Type<'a>>) -> ParseResult<Type<'a>> {
parser: &mut Parser<'a>,
inner: Spanning<Type<'a>>,
) -> ParseResult<'a, Type<'a>> {
let Spanning { end: end_pos, .. } = parser.expect(&Token::ExclamationMark)?; let Spanning { end: end_pos, .. } = parser.expect(&Token::ExclamationMark)?;
let wrapped = match inner.item { let wrapped = match inner.item {

View file

@ -20,8 +20,8 @@ pub struct Lexer<'a> {
/// A single scalar value literal /// A single scalar value literal
/// ///
/// This is only used for tagging how the lexer has interpreted a value literal /// This is only used for tagging how the lexer has interpreted a value literal
#[derive(Debug, PartialEq, Clone, Copy)]
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScalarToken<'a> { pub enum ScalarToken<'a> {
String(&'a str), String(&'a str),
Float(&'a str), Float(&'a str),
@ -29,8 +29,8 @@ pub enum ScalarToken<'a> {
} }
/// A single token in the input source /// A single token in the input source
#[derive(Debug, PartialEq, Clone, Copy)]
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Token<'a> { pub enum Token<'a> {
Name(&'a str), Name(&'a str),
Scalar(ScalarToken<'a>), Scalar(ScalarToken<'a>),
@ -239,7 +239,7 @@ impl<'a> Lexer<'a> {
c if escaped => { c if escaped => {
return Err(Spanning::zero_width( return Err(Spanning::zero_width(
&old_pos, &old_pos,
LexerError::UnknownEscapeSequence(format!("\\{}", c)), LexerError::UnknownEscapeSequence(format!("\\{c}")),
)); ));
} }
'\\' => escaped = true, '\\' => escaped = true,
@ -305,14 +305,14 @@ impl<'a> Lexer<'a> {
if len != 4 { if len != 4 {
return Err(Spanning::zero_width( return Err(Spanning::zero_width(
start_pos, start_pos,
LexerError::UnknownEscapeSequence("\\u".to_owned() + escape), LexerError::UnknownEscapeSequence(format!("\\u{escape}")),
)); ));
} }
let code_point = u32::from_str_radix(escape, 16).map_err(|_| { let code_point = u32::from_str_radix(escape, 16).map_err(|_| {
Spanning::zero_width( Spanning::zero_width(
start_pos, start_pos,
LexerError::UnknownEscapeSequence("\\u".to_owned() + escape), LexerError::UnknownEscapeSequence(format!("\\u{escape}")),
) )
})?; })?;
@ -338,7 +338,7 @@ impl<'a> Lexer<'a> {
let mut end_idx = loop { let mut end_idx = loop {
if let Some((idx, ch)) = self.peek_char() { if let Some((idx, ch)) = self.peek_char() {
if ch.is_digit(10) || (ch == '-' && last_idx == start_idx) { if ch.is_ascii_digit() || (ch == '-' && last_idx == start_idx) {
if ch == '0' && last_char == '0' && last_idx == start_idx { if ch == '0' && last_char == '0' && last_idx == start_idx {
return Err(Spanning::zero_width( return Err(Spanning::zero_width(
&self.position, &self.position,
@ -367,7 +367,7 @@ impl<'a> Lexer<'a> {
self.next_char(); self.next_char();
end_idx = loop { end_idx = loop {
if let Some((idx, ch)) = self.peek_char() { if let Some((idx, ch)) = self.peek_char() {
if ch.is_digit(10) { if ch.is_ascii_digit() {
self.next_char(); self.next_char();
} else if last_idx == start_idx { } else if last_idx == start_idx {
return Err(Spanning::zero_width( return Err(Spanning::zero_width(
@ -396,7 +396,9 @@ impl<'a> Lexer<'a> {
end_idx = loop { end_idx = loop {
if let Some((idx, ch)) = self.peek_char() { if let Some((idx, ch)) = self.peek_char() {
if ch.is_digit(10) || (last_idx == start_idx && (ch == '-' || ch == '+')) { if ch.is_ascii_digit()
|| (last_idx == start_idx && (ch == '-' || ch == '+'))
{
self.next_char(); self.next_char();
} else if last_idx == start_idx { } else if last_idx == start_idx {
// 1e is not a valid floating point number // 1e is not a valid floating point number
@ -483,9 +485,9 @@ impl<'a> Iterator for Lexer<'a> {
impl<'a> fmt::Display for Token<'a> { impl<'a> fmt::Display for Token<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Token::Name(name) => write!(f, "{}", name), Token::Name(name) => write!(f, "{name}"),
Token::Scalar(ScalarToken::Int(s)) | Token::Scalar(ScalarToken::Float(s)) => { Token::Scalar(ScalarToken::Int(s)) | Token::Scalar(ScalarToken::Float(s)) => {
write!(f, "{}", s) write!(f, "{s}")
} }
Token::Scalar(ScalarToken::String(s)) => { Token::Scalar(ScalarToken::String(s)) => {
write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
@ -527,15 +529,15 @@ fn is_number_start(c: char) -> bool {
impl fmt::Display for LexerError { impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
LexerError::UnknownCharacter(c) => write!(f, "Unknown character \"{}\"", c), LexerError::UnknownCharacter(c) => write!(f, "Unknown character \"{c}\""),
LexerError::UnterminatedString => write!(f, "Unterminated string literal"), LexerError::UnterminatedString => write!(f, "Unterminated string literal"),
LexerError::UnknownCharacterInString(c) => { LexerError::UnknownCharacterInString(c) => {
write!(f, "Unknown character \"{}\" in string literal", c) write!(f, "Unknown character \"{c}\" in string literal")
} }
LexerError::UnknownEscapeSequence(ref s) => { LexerError::UnknownEscapeSequence(ref s) => {
write!(f, "Unknown escape sequence \"{}\" in string", s) write!(f, "Unknown escape sequence \"{s}\" in string")
} }
LexerError::UnexpectedCharacter(c) => write!(f, "Unexpected character \"{}\"", c), LexerError::UnexpectedCharacter(c) => write!(f, "Unexpected character \"{c}\""),
LexerError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), LexerError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"),
LexerError::InvalidNumber => write!(f, "Invalid number literal"), LexerError::InvalidNumber => write!(f, "Invalid number literal"),
} }

View file

@ -1,12 +1,16 @@
use std::{fmt, result::Result}; use std::{error::Error, fmt, result::Result};
use smartstring::alias::String;
use crate::parser::{Lexer, LexerError, Spanning, Token}; use crate::parser::{Lexer, LexerError, Spanning, Token};
/// Error while parsing a GraphQL query /// Error while parsing a GraphQL query
#[derive(Debug, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum ParseError<'a> { pub enum ParseError {
/// An unexpected token occurred in the source /// An unexpected token occurred in the source
UnexpectedToken(Token<'a>), // TODO: Previously was `Token<'a>`.
// Revisit on `graphql-parser` integration.
UnexpectedToken(String),
/// The input source abruptly ended /// The input source abruptly ended
UnexpectedEndOfFile, UnexpectedEndOfFile,
@ -18,14 +22,51 @@ pub enum ParseError<'a> {
ExpectedScalarError(&'static str), ExpectedScalarError(&'static str),
} }
#[doc(hidden)] impl fmt::Display for ParseError {
pub type ParseResult<'a, T> = Result<Spanning<T>, Spanning<ParseError<'a>>>; fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedToken(token) => write!(f, "Unexpected \"{token}\""),
Self::UnexpectedEndOfFile => write!(f, "Unexpected end of input"),
Self::LexerError(e) => e.fmt(f),
Self::ExpectedScalarError(e) => e.fmt(f),
}
}
}
impl Error for ParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::LexerError(e) => Some(e),
Self::ExpectedScalarError(_) | Self::UnexpectedToken(_) | Self::UnexpectedEndOfFile => {
None
}
}
}
}
impl ParseError {
/// Creates a [`ParseError::UnexpectedToken`] out of the provided [`Token`].
#[must_use]
pub fn unexpected_token(token: Token<'_>) -> Self {
use std::fmt::Write as _;
let mut s = String::new();
// PANIC: Unwrapping is OK here, as it may panic only on allocation
// error.
write!(s, "{token}").unwrap();
Self::UnexpectedToken(s)
}
}
#[doc(hidden)] #[doc(hidden)]
pub type UnlocatedParseResult<'a, T> = Result<T, Spanning<ParseError<'a>>>; pub type ParseResult<T> = Result<Spanning<T>, Spanning<ParseError>>;
#[doc(hidden)] #[doc(hidden)]
pub type OptionParseResult<'a, T> = Result<Option<Spanning<T>>, Spanning<ParseError<'a>>>; pub type UnlocatedParseResult<T> = Result<T, Spanning<ParseError>>;
#[doc(hidden)]
pub type OptionParseResult<T> = Result<Option<Spanning<T>>, Spanning<ParseError>>;
#[doc(hidden)] #[doc(hidden)]
#[derive(Debug)] #[derive(Debug)]
@ -54,7 +95,7 @@ impl<'a> Parser<'a> {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn next_token(&mut self) -> ParseResult<'a, Token<'a>> { pub fn next_token(&mut self) -> ParseResult<Token<'a>> {
if self.tokens.len() == 1 { if self.tokens.len() == 1 {
Err(Spanning::start_end( Err(Spanning::start_end(
&self.peek().start, &self.peek().start,
@ -67,9 +108,9 @@ impl<'a> Parser<'a> {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn expect(&mut self, expected: &Token) -> ParseResult<'a, Token<'a>> { pub fn expect(&mut self, expected: &Token) -> ParseResult<Token<'a>> {
if &self.peek().item != expected { if &self.peek().item != expected {
Err(self.next_token()?.map(ParseError::UnexpectedToken)) Err(self.next_token()?.map(ParseError::unexpected_token))
} else { } else {
self.next_token() self.next_token()
} }
@ -79,7 +120,7 @@ impl<'a> Parser<'a> {
pub fn skip( pub fn skip(
&mut self, &mut self,
expected: &Token, expected: &Token,
) -> Result<Option<Spanning<Token<'a>>>, Spanning<ParseError<'a>>> { ) -> Result<Option<Spanning<Token<'a>>>, Spanning<ParseError>> {
if &self.peek().item == expected { if &self.peek().item == expected {
Ok(Some(self.next_token()?)) Ok(Some(self.next_token()?))
} else if self.peek().item == Token::EndOfFile { } else if self.peek().item == Token::EndOfFile {
@ -98,10 +139,10 @@ impl<'a> Parser<'a> {
opening: &Token, opening: &Token,
parser: F, parser: F,
closing: &Token, closing: &Token,
) -> ParseResult<'a, Vec<Spanning<T>>> ) -> ParseResult<Vec<Spanning<T>>>
where where
T: fmt::Debug, T: fmt::Debug,
F: Fn(&mut Parser<'a>) -> ParseResult<'a, T>, F: Fn(&mut Parser<'a>) -> ParseResult<T>,
{ {
let Spanning { let Spanning {
start: start_pos, .. start: start_pos, ..
@ -123,10 +164,10 @@ impl<'a> Parser<'a> {
opening: &Token, opening: &Token,
parser: F, parser: F,
closing: &Token, closing: &Token,
) -> ParseResult<'a, Vec<Spanning<T>>> ) -> ParseResult<Vec<Spanning<T>>>
where where
T: fmt::Debug, T: fmt::Debug,
F: Fn(&mut Parser<'a>) -> ParseResult<'a, T>, F: Fn(&mut Parser<'a>) -> ParseResult<T>,
{ {
let Spanning { let Spanning {
start: start_pos, .. start: start_pos, ..
@ -148,10 +189,10 @@ impl<'a> Parser<'a> {
opening: &Token, opening: &Token,
parser: F, parser: F,
closing: &Token, closing: &Token,
) -> ParseResult<'a, Vec<T>> ) -> ParseResult<Vec<T>>
where where
T: fmt::Debug, T: fmt::Debug,
F: Fn(&mut Parser<'a>) -> UnlocatedParseResult<'a, T>, F: Fn(&mut Parser<'a>) -> UnlocatedParseResult<T>,
{ {
let Spanning { let Spanning {
start: start_pos, .. start: start_pos, ..
@ -168,7 +209,7 @@ impl<'a> Parser<'a> {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn expect_name(&mut self) -> ParseResult<'a, &'a str> { pub fn expect_name(&mut self) -> ParseResult<&'a str> {
match *self.peek() { match *self.peek() {
Spanning { Spanning {
item: Token::Name(_), item: Token::Name(_),
@ -188,20 +229,7 @@ impl<'a> Parser<'a> {
&self.peek().end, &self.peek().end,
ParseError::UnexpectedEndOfFile, ParseError::UnexpectedEndOfFile,
)), )),
_ => Err(self.next_token()?.map(ParseError::UnexpectedToken)), _ => Err(self.next_token()?.map(ParseError::unexpected_token)),
} }
} }
} }
impl<'a> fmt::Display for ParseError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseError::UnexpectedToken(ref token) => write!(f, "Unexpected \"{}\"", token),
ParseError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"),
ParseError::LexerError(ref err) => err.fmt(f),
ParseError::ExpectedScalarError(err) => err.fmt(f),
}
}
}
impl<'a> std::error::Error for ParseError<'a> {}

View file

@ -16,18 +16,15 @@ where
s, s,
&SchemaType::new::<QueryRoot, MutationRoot, SubscriptionRoot>(&(), &(), &()), &SchemaType::new::<QueryRoot, MutationRoot, SubscriptionRoot>(&(), &(), &()),
) )
.expect(&format!("Parse error on input {:#?}", s)) .expect(&format!("Parse error on input {s:#?}"))
} }
fn parse_document_error<'a, S>(s: &'a str) -> Spanning<ParseError<'a>> fn parse_document_error<S: ScalarValue>(s: &str) -> Spanning<ParseError> {
where
S: ScalarValue,
{
match parse_document_source::<S>( match parse_document_source::<S>(
s, s,
&SchemaType::new::<QueryRoot, MutationRoot, SubscriptionRoot>(&(), &(), &()), &SchemaType::new::<QueryRoot, MutationRoot, SubscriptionRoot>(&(), &(), &()),
) { ) {
Ok(doc) => panic!("*No* parse error on input {:#?} =>\n{:#?}", s, doc), Ok(doc) => panic!("*No* parse error on input {s:#?} =>\n{doc:#?}"),
Err(err) => err, Err(err) => err,
} }
} }
@ -136,7 +133,7 @@ fn errors() {
Spanning::start_end( Spanning::start_end(
&SourcePosition::new(36, 1, 19), &SourcePosition::new(36, 1, 19),
&SourcePosition::new(40, 1, 23), &SourcePosition::new(40, 1, 23),
ParseError::UnexpectedToken(Token::Name("Type")) ParseError::UnexpectedToken("Type".into())
) )
); );
@ -145,7 +142,7 @@ fn errors() {
Spanning::start_end( Spanning::start_end(
&SourcePosition::new(8, 0, 8), &SourcePosition::new(8, 0, 8),
&SourcePosition::new(9, 0, 9), &SourcePosition::new(9, 0, 9),
ParseError::UnexpectedToken(Token::CurlyClose) ParseError::unexpected_token(Token::CurlyClose)
) )
); );
} }

View file

@ -13,8 +13,8 @@ fn tokenize_to_vec<'a>(s: &'a str) -> Vec<Spanning<Token<'a>>> {
break; break;
} }
} }
Some(Err(e)) => panic!("Error in input stream: {:#?} for {:#?}", e, s), Some(Err(e)) => panic!("Error in input stream: {e:#?} for {s:#?}"),
None => panic!("EOF before EndOfFile token in {:#?}", s), None => panic!("EOF before EndOfFile token in {s:#?}"),
} }
} }
@ -37,13 +37,13 @@ fn tokenize_error(s: &str) -> Spanning<LexerError> {
match lexer.next() { match lexer.next() {
Some(Ok(t)) => { Some(Ok(t)) => {
if t.item == Token::EndOfFile { if t.item == Token::EndOfFile {
panic!("Tokenizer did not return error for {:#?}", s); panic!("Tokenizer did not return error for {s:#?}");
} }
} }
Some(Err(e)) => { Some(Err(e)) => {
return e; return e;
} }
None => panic!("Tokenizer did not return error for {:#?}", s), None => panic!("Tokenizer did not return error for {s:#?}"),
} }
} }
} }
@ -196,7 +196,7 @@ fn strings() {
Spanning::start_end( Spanning::start_end(
&SourcePosition::new(0, 0, 0), &SourcePosition::new(0, 0, 0),
&SourcePosition::new(34, 0, 34), &SourcePosition::new(34, 0, 34),
Token::Scalar(ScalarToken::String(r#"unicode \u1234\u5678\u90AB\uCDEF"#)) Token::Scalar(ScalarToken::String(r#"unicode \u1234\u5678\u90AB\uCDEF"#)),
) )
); );
} }
@ -207,7 +207,7 @@ fn string_errors() {
tokenize_error("\""), tokenize_error("\""),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(1, 0, 1), &SourcePosition::new(1, 0, 1),
LexerError::UnterminatedString LexerError::UnterminatedString,
) )
); );
@ -215,7 +215,7 @@ fn string_errors() {
tokenize_error("\"no end quote"), tokenize_error("\"no end quote"),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(13, 0, 13), &SourcePosition::new(13, 0, 13),
LexerError::UnterminatedString LexerError::UnterminatedString,
) )
); );
@ -223,7 +223,7 @@ fn string_errors() {
tokenize_error("\"contains unescaped \u{0007} control char\""), tokenize_error("\"contains unescaped \u{0007} control char\""),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(20, 0, 20), &SourcePosition::new(20, 0, 20),
LexerError::UnknownCharacterInString('\u{0007}') LexerError::UnknownCharacterInString('\u{0007}'),
) )
); );
@ -231,7 +231,7 @@ fn string_errors() {
tokenize_error("\"null-byte is not \u{0000} end of file\""), tokenize_error("\"null-byte is not \u{0000} end of file\""),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(18, 0, 18), &SourcePosition::new(18, 0, 18),
LexerError::UnknownCharacterInString('\u{0000}') LexerError::UnknownCharacterInString('\u{0000}'),
) )
); );
@ -239,7 +239,7 @@ fn string_errors() {
tokenize_error("\"multi\nline\""), tokenize_error("\"multi\nline\""),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnterminatedString LexerError::UnterminatedString,
) )
); );
@ -247,7 +247,7 @@ fn string_errors() {
tokenize_error("\"multi\rline\""), tokenize_error("\"multi\rline\""),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnterminatedString LexerError::UnterminatedString,
) )
); );
@ -255,7 +255,7 @@ fn string_errors() {
tokenize_error(r#""bad \z esc""#), tokenize_error(r#""bad \z esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\z".to_owned()) LexerError::UnknownEscapeSequence("\\z".into()),
) )
); );
@ -263,7 +263,7 @@ fn string_errors() {
tokenize_error(r#""bad \x esc""#), tokenize_error(r#""bad \x esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\x".to_owned()) LexerError::UnknownEscapeSequence("\\x".into()),
) )
); );
@ -271,7 +271,7 @@ fn string_errors() {
tokenize_error(r#""bad \u1 esc""#), tokenize_error(r#""bad \u1 esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\u1".to_owned()) LexerError::UnknownEscapeSequence("\\u1".into()),
) )
); );
@ -279,7 +279,7 @@ fn string_errors() {
tokenize_error(r#""bad \u0XX1 esc""#), tokenize_error(r#""bad \u0XX1 esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\u0XX1".to_owned()) LexerError::UnknownEscapeSequence("\\u0XX1".into()),
) )
); );
@ -287,7 +287,7 @@ fn string_errors() {
tokenize_error(r#""bad \uXXXX esc""#), tokenize_error(r#""bad \uXXXX esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\uXXXX".to_owned()) LexerError::UnknownEscapeSequence("\\uXXXX".into()),
) )
); );
@ -295,7 +295,7 @@ fn string_errors() {
tokenize_error(r#""bad \uFXXX esc""#), tokenize_error(r#""bad \uFXXX esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\uFXXX".to_owned()) LexerError::UnknownEscapeSequence("\\uFXXX".into()),
) )
); );
@ -303,7 +303,7 @@ fn string_errors() {
tokenize_error(r#""bad \uXXXF esc""#), tokenize_error(r#""bad \uXXXF esc""#),
Spanning::zero_width( Spanning::zero_width(
&SourcePosition::new(6, 0, 6), &SourcePosition::new(6, 0, 6),
LexerError::UnknownEscapeSequence("\\uXXXF".to_owned()) LexerError::UnknownEscapeSequence("\\uXXXF".into()),
) )
); );
@ -349,9 +349,7 @@ fn numbers() {
Token::Scalar(ScalarToken::Float(actual)) => { Token::Scalar(ScalarToken::Float(actual)) => {
assert!( assert!(
expected == actual, expected == actual,
"[expected] {} != {} [actual]", "[expected] {expected} != {actual} [actual]",
expected,
actual
); );
} }
_ => assert!(false), _ => assert!(false),
@ -662,39 +660,32 @@ fn punctuation_error() {
#[test] #[test]
fn display() { fn display() {
assert_eq!(format!("{}", Token::Name("identifier")), "identifier"); for (input, expected) in [
(Token::Name("identifier"), "identifier"),
assert_eq!(format!("{}", Token::Scalar(ScalarToken::Int("123"))), "123"); (Token::Scalar(ScalarToken::Int("123")), "123"),
(Token::Scalar(ScalarToken::Float("4.5")), "4.5"),
assert_eq!( (
format!("{}", Token::Scalar(ScalarToken::Float("4.5"))), Token::Scalar(ScalarToken::String("some string")),
"4.5" "\"some string\"",
);
assert_eq!(
format!("{}", Token::Scalar(ScalarToken::String("some string"))),
"\"some string\""
);
assert_eq!(
format!(
"{}",
Token::Scalar(ScalarToken::String("string with \\ escape and \" quote"))
), ),
"\"string with \\\\ escape and \\\" quote\"" (
); Token::Scalar(ScalarToken::String("string with \\ escape and \" quote")),
"\"string with \\\\ escape and \\\" quote\"",
assert_eq!(format!("{}", Token::ExclamationMark), "!"); ),
assert_eq!(format!("{}", Token::Dollar), "$"); (Token::ExclamationMark, "!"),
assert_eq!(format!("{}", Token::ParenOpen), "("); (Token::Dollar, "$"),
assert_eq!(format!("{}", Token::ParenClose), ")"); (Token::ParenOpen, "("),
assert_eq!(format!("{}", Token::BracketOpen), "["); (Token::ParenClose, ")"),
assert_eq!(format!("{}", Token::BracketClose), "]"); (Token::BracketOpen, "["),
assert_eq!(format!("{}", Token::CurlyOpen), "{"); (Token::BracketClose, "]"),
assert_eq!(format!("{}", Token::CurlyClose), "}"); (Token::CurlyOpen, "{"),
assert_eq!(format!("{}", Token::Ellipsis), "..."); (Token::CurlyClose, "}"),
assert_eq!(format!("{}", Token::Colon), ":"); (Token::Ellipsis, "..."),
assert_eq!(format!("{}", Token::Equals), "="); (Token::Colon, ":"),
assert_eq!(format!("{}", Token::At), "@"); (Token::Equals, "="),
assert_eq!(format!("{}", Token::Pipe), "|"); (Token::At, "@"),
(Token::Pipe, "|"),
] {
assert_eq!(input.to_string(), expected);
}
} }

View file

@ -65,11 +65,11 @@ where
S: ScalarValue, S: ScalarValue,
{ {
let mut lexer = Lexer::new(s); let mut lexer = Lexer::new(s);
let mut parser = Parser::new(&mut lexer).expect(&format!("Lexer error on input {:#?}", s)); let mut parser = Parser::new(&mut lexer).expect(&format!("Lexer error on input {s:#?}"));
let schema = SchemaType::new::<Query, EmptyMutation<()>, EmptySubscription<()>>(&(), &(), &()); let schema = SchemaType::new::<Query, EmptyMutation<()>, EmptySubscription<()>>(&(), &(), &());
parse_value_literal(&mut parser, false, &schema, Some(meta)) parse_value_literal(&mut parser, false, &schema, Some(meta))
.expect(&format!("Parse error on input {:#?}", s)) .expect(&format!("Parse error on input {s:#?}"))
} }
#[test] #[test]

View file

@ -81,7 +81,7 @@ impl<T> Spanning<T> {
} }
} }
/// Modify the contents of the spanned item /// Modify the contents of the spanned item.
pub fn map<O, F: Fn(T) -> O>(self, f: F) -> Spanning<O> { pub fn map<O, F: Fn(T) -> O>(self, f: F) -> Spanning<O> {
Spanning { Spanning {
item: f(self.item), item: f(self.item),
@ -89,6 +89,13 @@ impl<T> Spanning<T> {
end: self.end, end: self.end,
} }
} }
/// Modifies the contents of the spanned item in case `f` returns [`Some`],
/// or returns [`None`] otherwise.
pub fn and_then<O, F: Fn(T) -> Option<O>>(self, f: F) -> Option<Spanning<O>> {
let (start, end) = (self.start, self.end);
f(self.item).map(|item| Spanning { item, start, end })
}
} }
impl<T: fmt::Display> fmt::Display for Spanning<T> { impl<T: fmt::Display> fmt::Display for Spanning<T> {

View file

@ -9,12 +9,12 @@ use crate::{
value::ScalarValue, value::ScalarValue,
}; };
pub fn parse_value_literal<'a, 'b, S>( pub fn parse_value_literal<'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'_>,
is_const: bool, is_const: bool,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
tpe: Option<&MetaType<'b, S>>, tpe: Option<&MetaType<'b, S>>,
) -> ParseResult<'a, InputValue<S>> ) -> ParseResult<InputValue<S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -113,16 +113,16 @@ where
}, },
_, _,
) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))),
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
} }
} }
fn parse_list_literal<'a, 'b, S>( fn parse_list_literal<'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'_>,
is_const: bool, is_const: bool,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
tpe: Option<&MetaType<'b, S>>, tpe: Option<&MetaType<'b, S>>,
) -> ParseResult<'a, InputValue<S>> ) -> ParseResult<InputValue<S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -135,12 +135,12 @@ where
.map(InputValue::parsed_list)) .map(InputValue::parsed_list))
} }
fn parse_object_literal<'a, 'b, S>( fn parse_object_literal<'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'_>,
is_const: bool, is_const: bool,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
object_tpe: Option<&InputObjectMeta<'b, S>>, object_tpe: Option<&InputObjectMeta<'b, S>>,
) -> ParseResult<'a, InputValue<S>> ) -> ParseResult<InputValue<S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -153,12 +153,12 @@ where
.map(|items| InputValue::parsed_object(items.into_iter().map(|s| s.item).collect()))) .map(|items| InputValue::parsed_object(items.into_iter().map(|s| s.item).collect())))
} }
fn parse_object_field<'a, 'b, S>( fn parse_object_field<'b, S>(
parser: &mut Parser<'a>, parser: &mut Parser<'_>,
is_const: bool, is_const: bool,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
object_meta: Option<&InputObjectMeta<'b, S>>, object_meta: Option<&InputObjectMeta<'b, S>>,
) -> ParseResult<'a, (Spanning<String>, Spanning<InputValue<S>>)> ) -> ParseResult<(Spanning<String>, Spanning<InputValue<S>>)>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -179,7 +179,7 @@ where
)) ))
} }
fn parse_variable_literal<'a, S>(parser: &mut Parser<'a>) -> ParseResult<'a, InputValue<S>> fn parse_variable_literal<S>(parser: &mut Parser<'_>) -> ParseResult<InputValue<S>>
where where
S: ScalarValue, S: ScalarValue,
{ {
@ -199,12 +199,12 @@ where
)) ))
} }
fn parse_scalar_literal_by_infered_type<'a, 'b, S>( fn parse_scalar_literal_by_infered_type<'b, S>(
token: ScalarToken<'a>, token: ScalarToken<'_>,
start: &SourcePosition, start: &SourcePosition,
end: &SourcePosition, end: &SourcePosition,
schema: &'b SchemaType<'b, S>, schema: &'b SchemaType<'b, S>,
) -> ParseResult<'a, InputValue<S>> ) -> ParseResult<InputValue<S>>
where where
S: ScalarValue, S: ScalarValue,
{ {

View file

@ -16,7 +16,7 @@ use crate::{
}; };
/// Whether an item is deprecated, with context. /// Whether an item is deprecated, with context.
#[derive(Debug, PartialEq, Hash, Clone)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum DeprecationStatus { pub enum DeprecationStatus {
/// The field/variant is not deprecated. /// The field/variant is not deprecated.
Current, Current,
@ -58,7 +58,7 @@ pub struct ScalarMeta<'a, S> {
pub type InputValueParseFn<S> = for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>; pub type InputValueParseFn<S> = for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>;
/// Shortcut for a [`ScalarToken`] parsing function. /// Shortcut for a [`ScalarToken`] parsing function.
pub type ScalarTokenParseFn<S> = for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError<'b>>; pub type ScalarTokenParseFn<S> = for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError>;
/// List type metadata /// List type metadata
#[derive(Debug)] #[derive(Debug)]
@ -110,6 +110,8 @@ pub struct InterfaceMeta<'a, S> {
pub description: Option<String>, pub description: Option<String>,
#[doc(hidden)] #[doc(hidden)]
pub fields: Vec<Field<'a, S>>, pub fields: Vec<Field<'a, S>>,
#[doc(hidden)]
pub interface_names: Vec<String>,
} }
/// Union type metadata /// Union type metadata
@ -403,7 +405,7 @@ impl<'a, S> MetaType<'a, S> {
// "used exclusively by GraphQLs introspection system" // "used exclusively by GraphQLs introspection system"
{ {
name.starts_with("__") || name.starts_with("__") ||
// <https://facebook.github.io/graphql/draft/#sec-Scalars> // https://spec.graphql.org/October2021#sec-Scalars
name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" || name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" ||
// Our custom empty markers // Our custom empty markers
name == "_EmptyMutation" || name == "_EmptySubscription" name == "_EmptyMutation" || name == "_EmptySubscription"
@ -453,7 +455,7 @@ impl<'a, S> ScalarMeta<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -523,7 +525,7 @@ impl<'a, S> ObjectMeta<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -534,7 +536,7 @@ impl<'a, S> ObjectMeta<'a, S> {
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
self.interface_names = interfaces self.interface_names = interfaces
.iter() .iter()
.map(|t| t.innermost_name().to_owned()) .map(|t| t.innermost_name().into())
.collect(); .collect();
self self
} }
@ -566,7 +568,7 @@ impl<'a, S> EnumMeta<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -587,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> {
name, name,
description: None, description: None,
fields: fields.to_vec(), fields: fields.to_vec(),
interface_names: Vec::new(),
} }
} }
@ -595,7 +598,19 @@ impl<'a, S> InterfaceMeta<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self
}
/// Sets the `interfaces` this [`InterfaceMeta`] interface implements.
///
/// Overwrites any previously set list of interfaces.
#[must_use]
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
self.interface_names = interfaces
.iter()
.map(|t| t.innermost_name().into())
.collect();
self self
} }
@ -612,10 +627,7 @@ impl<'a> UnionMeta<'a> {
Self { Self {
name, name,
description: None, description: None,
of_type_names: of_types of_type_names: of_types.iter().map(|t| t.innermost_name().into()).collect(),
.iter()
.map(|t| t.innermost_name().to_owned())
.collect(),
} }
} }
@ -624,7 +636,7 @@ impl<'a> UnionMeta<'a> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -656,7 +668,7 @@ impl<'a, S> InputObjectMeta<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -672,7 +684,7 @@ impl<'a, S> Field<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -697,7 +709,7 @@ impl<'a, S> Field<'a, S> {
/// Overwrites any previously set deprecation reason. /// Overwrites any previously set deprecation reason.
#[must_use] #[must_use]
pub fn deprecated(mut self, reason: Option<&str>) -> Self { pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into));
self self
} }
} }
@ -706,7 +718,7 @@ impl<'a, S> Argument<'a, S> {
/// Builds a new [`Argument`] of the given [`Type`] with the given `name`. /// Builds a new [`Argument`] of the given [`Type`] with the given `name`.
pub fn new(name: &str, arg_type: Type<'a>) -> Self { pub fn new(name: &str, arg_type: Type<'a>) -> Self {
Self { Self {
name: name.to_owned(), name: name.into(),
description: None, description: None,
arg_type, arg_type,
default_value: None, default_value: None,
@ -718,7 +730,7 @@ impl<'a, S> Argument<'a, S> {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -736,7 +748,7 @@ impl EnumValue {
/// Constructs a new [`EnumValue`] with the provided `name`. /// Constructs a new [`EnumValue`] with the provided `name`.
pub fn new(name: &str) -> Self { pub fn new(name: &str) -> Self {
Self { Self {
name: name.to_owned(), name: name.into(),
description: None, description: None,
deprecation_status: DeprecationStatus::Current, deprecation_status: DeprecationStatus::Current,
} }
@ -747,7 +759,7 @@ impl EnumValue {
/// Overwrites any previously set description. /// Overwrites any previously set description.
#[must_use] #[must_use]
pub fn description(mut self, description: &str) -> Self { pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
@ -756,7 +768,7 @@ impl EnumValue {
/// Overwrites any previously set deprecation reason. /// Overwrites any previously set deprecation reason.
#[must_use] #[must_use]
pub fn deprecated(mut self, reason: Option<&str>) -> Self { pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into));
self self
} }
} }

View file

@ -167,8 +167,7 @@ where
/// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language) /// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
/// format. /// format.
pub fn as_schema_language(&self) -> String { pub fn as_schema_language(&self) -> String {
let doc = self.as_parser_document(); self.as_parser_document().to_string()
format!("{}", doc)
} }
#[cfg(feature = "graphql-parser")] #[cfg(feature = "graphql-parser")]
@ -210,17 +209,14 @@ impl<'a, S> SchemaType<'a, S> {
registry.get_type::<SchemaType<S>>(&()); registry.get_type::<SchemaType<S>>(&());
directives.insert("skip".to_owned(), DirectiveType::new_skip(&mut registry)); directives.insert("skip".into(), DirectiveType::new_skip(&mut registry));
directives.insert("include".into(), DirectiveType::new_include(&mut registry));
directives.insert( directives.insert(
"include".to_owned(), "deprecated".into(),
DirectiveType::new_include(&mut registry),
);
directives.insert(
"deprecated".to_owned(),
DirectiveType::new_deprecated(&mut registry), DirectiveType::new_deprecated(&mut registry),
); );
directives.insert( directives.insert(
"specifiedBy".to_owned(), "specifiedBy".into(),
DirectiveType::new_specified_by(&mut registry), DirectiveType::new_specified_by(&mut registry),
); );
@ -243,7 +239,7 @@ impl<'a, S> SchemaType<'a, S> {
for meta_type in registry.types.values() { for meta_type in registry.types.values() {
if let MetaType::Placeholder(PlaceholderMeta { ref of_type }) = *meta_type { if let MetaType::Placeholder(PlaceholderMeta { ref of_type }) = *meta_type {
panic!("Type {:?} is still a placeholder type", of_type); panic!("Type {of_type:?} is still a placeholder type");
} }
} }
SchemaType { SchemaType {
@ -508,9 +504,9 @@ where
locations: &[DirectiveLocation], locations: &[DirectiveLocation],
arguments: &[Argument<'a, S>], arguments: &[Argument<'a, S>],
is_repeatable: bool, is_repeatable: bool,
) -> DirectiveType<'a, S> { ) -> Self {
DirectiveType { Self {
name: name.to_owned(), name: name.into(),
description: None, description: None,
locations: locations.to_vec(), locations: locations.to_vec(),
arguments: arguments.to_vec(), arguments: arguments.to_vec(),
@ -578,7 +574,7 @@ where
} }
pub fn description(mut self, description: &str) -> DirectiveType<'a, S> { pub fn description(mut self, description: &str) -> DirectiveType<'a, S> {
self.description = Some(description.to_owned()); self.description = Some(description.into());
self self
} }
} }
@ -605,8 +601,8 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::Concrete(t) => f.write_str(t.name().unwrap()), Self::Concrete(t) => f.write_str(t.name().unwrap()),
Self::List(i, _) => write!(f, "[{}]", i), Self::List(i, _) => write!(f, "[{i}]"),
Self::NonNull(i) => write!(f, "{}!", i), Self::NonNull(i) => write!(f, "{i}!"),
} }
} }
} }
@ -644,10 +640,7 @@ mod test {
"#, "#,
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(ast.to_string(), schema.as_parser_document().to_string());
format!("{}", ast),
format!("{}", schema.as_parser_document()),
);
} }
} }
@ -691,10 +684,10 @@ mod test {
} }
/// This is whatever's description. /// This is whatever's description.
fn whatever() -> String { fn whatever() -> String {
"foo".to_string() "foo".into()
} }
fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> { fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> {
(!stuff.is_empty()).then(|| "stuff") (!stuff.is_empty()).then_some("stuff")
} }
fn fruit() -> Fruit { fn fruit() -> Fruit {
Fruit::Apple Fruit::Apple
@ -754,7 +747,7 @@ mod test {
"#, "#,
) )
.unwrap(); .unwrap();
assert_eq!(format!("{}", ast), schema.as_schema_language()); assert_eq!(ast.to_string(), schema.as_schema_language());
} }
} }
} }

View file

@ -211,13 +211,19 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
} }
} }
fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&Field<S>>> { fn fields(
&self,
#[graphql(default = false)] include_deprecated: Option<bool>,
) -> Option<Vec<&Field<S>>> {
match self { match self {
TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
| TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some(
fields fields
.iter() .iter()
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .filter(|f| {
include_deprecated.unwrap_or_default()
|| !f.deprecation_status.is_deprecated()
})
.filter(|f| !f.name.starts_with("__")) .filter(|f| !f.name.starts_with("__"))
.collect(), .collect(),
), ),
@ -228,7 +234,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
fn of_type(&self) -> Option<&TypeType<S>> { fn of_type(&self) -> Option<&TypeType<S>> {
match self { match self {
TypeType::Concrete(_) => None, TypeType::Concrete(_) => None,
TypeType::List(l, _) | TypeType::NonNull(l) => Some(&*l), TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l),
} }
} }
@ -244,10 +250,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> { fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> {
match self { match self {
TypeType::Concrete(&MetaType::Object(ObjectMeta { TypeType::Concrete(
ref interface_names, &MetaType::Object(ObjectMeta {
.. ref interface_names,
})) => Some( ..
})
| &MetaType::Interface(InterfaceMeta {
ref interface_names,
..
}),
) => Some(
interface_names interface_names
.iter() .iter()
.filter_map(|n| context.type_by_name(n)) .filter_map(|n| context.type_by_name(n))
@ -276,16 +288,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
.iter() .iter()
.filter_map(|&ct| { .filter_map(|&ct| {
if let MetaType::Object(ObjectMeta { if let MetaType::Object(ObjectMeta {
ref name, name,
ref interface_names, interface_names,
.. ..
}) = *ct }) = ct
{ {
if interface_names.contains(&iface_name.to_string()) { interface_names
context.type_by_name(name) .iter()
} else { .any(|name| name == iface_name)
None .then(|| context.type_by_name(name))
} .flatten()
} else { } else {
None None
} }
@ -296,12 +308,18 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
} }
} }
fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option<Vec<&EnumValue>> { fn enum_values(
&self,
#[graphql(default = false)] include_deprecated: Option<bool>,
) -> Option<Vec<&EnumValue>> {
match self { match self {
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some(
values values
.iter() .iter()
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .filter(|f| {
include_deprecated.unwrap_or_default()
|| !f.deprecation_status.is_deprecated()
})
.collect(), .collect(),
), ),
_ => None, _ => None,

View file

@ -190,8 +190,11 @@ impl GraphQLParserTranslator {
position: Pos::default(), position: Pos::default(),
description: x.description.as_ref().map(|s| From::from(s.as_str())), description: x.description.as_ref().map(|s| From::from(s.as_str())),
name: From::from(x.name.as_ref()), name: From::from(x.name.as_ref()),
// TODO: Support this with GraphQL October 2021 Edition. implements_interfaces: x
implements_interfaces: vec![], .interface_names
.iter()
.map(|s| From::from(s.as_str()))
.collect(),
directives: vec![], directives: vec![],
fields: x fields: x
.fields .fields
@ -282,22 +285,18 @@ where
DeprecationStatus::Current => None, DeprecationStatus::Current => None,
DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { DeprecationStatus::Deprecated(reason) => Some(ExternalDirective {
position: Pos::default(), position: Pos::default(),
name: From::from("deprecated"), name: "deprecated".into(),
arguments: if let Some(reason) = reason { arguments: reason
vec![( .as_ref()
From::from("reason"), .map(|rsn| vec![(From::from("reason"), ExternalValue::String(rsn.into()))])
ExternalValue::String(reason.to_string()), .unwrap_or_default(),
)]
} else {
vec![]
},
}), }),
} }
} }
// Right now the only directive supported is `@deprecated`. `@skip` and `@include` // Right now the only directive supported is `@deprecated`.
// are dealt with elsewhere. // `@skip` and `@include` are dealt with elsewhere.
// <https://facebook.github.io/graphql/draft/#sec-Type-System.Directives> // https://spec.graphql.org/October2021#sec-Type-System.Directives.Built-in-Directives
fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec<ExternalDirective<'a, T>> fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec<ExternalDirective<'a, T>>
where where
T: Text<'a>, T: Text<'a>,

View file

@ -93,12 +93,12 @@ impl Human {
home_planet: Option<&str>, home_planet: Option<&str>,
) -> Self { ) -> Self {
Self { Self {
id: id.to_owned(), id: id.into(),
name: name.to_owned(), name: name.into(),
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), friend_ids: friend_ids.iter().copied().map(Into::into).collect(),
appears_in: appears_in.to_vec(), appears_in: appears_in.to_vec(),
secret_backstory: secret_backstory.map(ToOwned::to_owned), secret_backstory: secret_backstory.map(Into::into),
home_planet: home_planet.map(|p| p.to_owned()), home_planet: home_planet.map(Into::into),
} }
} }
} }
@ -153,12 +153,12 @@ impl Droid {
primary_function: Option<&str>, primary_function: Option<&str>,
) -> Self { ) -> Self {
Self { Self {
id: id.to_owned(), id: id.into(),
name: name.to_owned(), name: name.into(),
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), friend_ids: friend_ids.iter().copied().map(Into::into).collect(),
appears_in: appears_in.to_vec(), appears_in: appears_in.to_vec(),
secret_backstory: secret_backstory.map(ToOwned::to_owned), secret_backstory: secret_backstory.map(Into::into),
primary_function: primary_function.map(ToOwned::to_owned), primary_function: primary_function.map(Into::into),
} }
} }
} }
@ -192,7 +192,7 @@ impl Droid {
} }
} }
#[derive(Default, Clone)] #[derive(Clone, Default)]
pub struct Database { pub struct Database {
humans: HashMap<String, Human>, humans: HashMap<String, Human>,
droids: HashMap<String, Droid>, droids: HashMap<String, Droid>,
@ -206,7 +206,7 @@ impl Database {
let mut droids = HashMap::new(); let mut droids = HashMap::new();
humans.insert( humans.insert(
"1000".to_owned(), "1000".into(),
Human::new( Human::new(
"1000", "1000",
"Luke Skywalker", "Luke Skywalker",
@ -218,7 +218,7 @@ impl Database {
); );
humans.insert( humans.insert(
"1001".to_owned(), "1001".into(),
Human::new( Human::new(
"1001", "1001",
"Darth Vader", "Darth Vader",
@ -230,7 +230,7 @@ impl Database {
); );
humans.insert( humans.insert(
"1002".to_owned(), "1002".into(),
Human::new( Human::new(
"1002", "1002",
"Han Solo", "Han Solo",
@ -242,7 +242,7 @@ impl Database {
); );
humans.insert( humans.insert(
"1003".to_owned(), "1003".into(),
Human::new( Human::new(
"1003", "1003",
"Leia Organa", "Leia Organa",
@ -254,7 +254,7 @@ impl Database {
); );
humans.insert( humans.insert(
"1004".to_owned(), "1004".into(),
Human::new( Human::new(
"1004", "1004",
"Wilhuff Tarkin", "Wilhuff Tarkin",
@ -266,7 +266,7 @@ impl Database {
); );
droids.insert( droids.insert(
"2000".to_owned(), "2000".into(),
Droid::new( Droid::new(
"2000", "2000",
"C-3PO", "C-3PO",
@ -278,7 +278,7 @@ impl Database {
); );
droids.insert( droids.insert(
"2001".to_owned(), "2001".into(),
Droid::new( Droid::new(
"2001", "2001",
"R2-D2", "R2-D2",

View file

@ -1173,7 +1173,7 @@ pub(crate) fn schema_introspection_result() -> Value {
} }
], ],
"inputFields": null, "inputFields": null,
"interfaces": null, "interfaces": [],
"enumValues": null, "enumValues": null,
"possibleTypes": [ "possibleTypes": [
{ {
@ -2500,7 +2500,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
} }
], ],
"inputFields": null, "inputFields": null,
"interfaces": null, "interfaces": [],
"enumValues": null, "enumValues": null,
"possibleTypes": [ "possibleTypes": [
{ {

View file

@ -1,4 +1,4 @@
use std::{iter, iter::FromIterator as _, pin::Pin}; use std::{iter, pin::Pin};
use futures::{stream, StreamExt as _}; use futures::{stream, StreamExt as _};
@ -48,9 +48,9 @@ impl MySubscription {
async fn async_human() -> HumanStream { async fn async_human() -> HumanStream {
Box::pin(stream::once(async { Box::pin(stream::once(async {
Human { Human {
id: "stream id".to_string(), id: "stream id".into(),
name: "stream name".to_string(), name: "stream name".into(),
home_planet: "stream home planet".to_string(), home_planet: "stream home planet".into(),
} }
})) }))
} }
@ -78,7 +78,7 @@ impl MySubscription {
Human { Human {
id, id,
name, name,
home_planet: "default home planet".to_string(), home_planet: "default home planet".into(),
} }
})) }))
} }
@ -154,10 +154,10 @@ fn returns_requested_object() {
id id
name name
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(
@ -182,10 +182,9 @@ fn returns_error() {
id id
name name
} }
}"# }"#;
.to_string();
let response = create_and_execute(query); let response = create_and_execute(query.into());
assert!(response.is_err()); assert!(response.is_err());
@ -206,10 +205,10 @@ fn can_access_context() {
humanWithContext { humanWithContext {
id id
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn(
@ -234,10 +233,10 @@ fn resolves_typed_inline_fragments() {
id id
} }
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn(
@ -262,10 +261,10 @@ fn resolves_nontyped_inline_fragments() {
id id
} }
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn(
@ -289,10 +288,10 @@ fn can_access_arguments() {
id id
name name
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn(
@ -317,10 +316,10 @@ fn type_alias() {
id id
name name
} }
}"# }"#;
.to_string();
let (names, collected_values) = create_and_execute(query).expect("Got error from stream"); let (names, collected_values) =
create_and_execute(query.into()).expect("Got error from stream");
let mut iterator_count = 0; let mut iterator_count = 0;
let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn(

View file

@ -75,15 +75,15 @@ fn test_node() {
baz baz
}"#; }"#;
let node_info = NodeTypeInfo { let node_info = NodeTypeInfo {
name: "MyNode".to_string(), name: "MyNode".into(),
attribute_names: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], attribute_names: vec!["foo".into(), "bar".into(), "baz".into()],
}; };
let mut node = Node { let mut node = Node {
attributes: IndexMap::new(), attributes: IndexMap::new(),
}; };
node.attributes.insert("foo".to_string(), "1".to_string()); node.attributes.insert("foo".into(), "1".into());
node.attributes.insert("bar".to_string(), "2".to_string()); node.attributes.insert("bar".into(), "2".into());
node.attributes.insert("baz".to_string(), "3".to_string()); node.attributes.insert("baz".into(), "3".into());
let schema: RootNode<_, _, _> = RootNode::new_with_info( let schema: RootNode<_, _, _> = RootNode::new_with_info(
node, node,
EmptyMutation::new(), EmptyMutation::new(),

View file

@ -1,3 +1,5 @@
use std::future;
use crate::{ use crate::{
ast::Selection, ast::Selection,
executor::{ExecutionResult, Executor}, executor::{ExecutionResult, Executor},
@ -30,7 +32,7 @@ where
/// ///
/// The default implementation panics. /// The default implementation panics.
/// ///
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve_field_async<'a>( fn resolve_field_async<'a>(
&'a self, &'a self,
_info: &'a Self::TypeInfo, _info: &'a Self::TypeInfo,
@ -54,9 +56,9 @@ where
/// ///
/// The default implementation panics. /// The default implementation panics.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
/// [2]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Unions
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve_into_type_async<'a>( fn resolve_into_type_async<'a>(
&'a self, &'a self,
info: &'a Self::TypeInfo, info: &'a Self::TypeInfo,
@ -91,8 +93,8 @@ where
/// ///
/// The default implementation panics, if `selection_set` is [`None`]. /// The default implementation panics, if `selection_set` is [`None`].
/// ///
/// [0]: https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability /// [0]: https://spec.graphql.org/October2021#sec-Handling-Field-Errors
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve_async<'a>( fn resolve_async<'a>(
&'a self, &'a self,
info: &'a Self::TypeInfo, info: &'a Self::TypeInfo,
@ -226,7 +228,7 @@ where
panic!( panic!(
"Field {} not found on type {:?}", "Field {} not found on type {:?}",
f.name.item, f.name.item,
meta_type.name() meta_type.name(),
) )
}); });
@ -242,7 +244,9 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) .filter_map(|&(ref k, ref v)| {
v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
})
.collect() .collect()
}), }),
&meta_field.arguments, &meta_field.arguments,
@ -252,7 +256,7 @@ where
let is_non_null = meta_field.field_type.is_non_null(); let is_non_null = meta_field.field_type.is_non_null();
let response_name = response_name.to_string(); let response_name = response_name.to_string();
async_values.push(AsyncValueFuture::Field(async move { async_values.push_back(AsyncValueFuture::Field(async move {
// TODO: implement custom future type instead of // TODO: implement custom future type instead of
// two-level boxing. // two-level boxing.
let res = instance let res = instance
@ -315,12 +319,12 @@ where
if let Ok(Value::Object(obj)) = sub_result { if let Ok(Value::Object(obj)) = sub_result {
for (k, v) in obj { for (k, v) in obj {
async_values.push(AsyncValueFuture::FragmentSpread(async move { async_values.push_back(AsyncValueFuture::FragmentSpread(
AsyncValue::Field(AsyncField { future::ready(AsyncValue::Field(AsyncField {
name: k, name: k,
value: Some(v), value: Some(v),
}) })),
})); ));
} }
} else if let Err(e) = sub_result { } else if let Err(e) = sub_result {
sub_exec.push_error_at(e, *start_pos); sub_exec.push_error_at(e, *start_pos);
@ -360,19 +364,19 @@ where
if let Ok(Value::Object(obj)) = sub_result { if let Ok(Value::Object(obj)) = sub_result {
for (k, v) in obj { for (k, v) in obj {
async_values.push(AsyncValueFuture::InlineFragment1(async move { async_values.push_back(AsyncValueFuture::InlineFragment1(
AsyncValue::Field(AsyncField { future::ready(AsyncValue::Field(AsyncField {
name: k, name: k,
value: Some(v), value: Some(v),
}) })),
})); ));
} }
} else if let Err(e) = sub_result { } else if let Err(e) = sub_result {
sub_exec.push_error_at(e, *start_pos); sub_exec.push_error_at(e, *start_pos);
} }
} }
} else { } else {
async_values.push(AsyncValueFuture::InlineFragment2(async move { async_values.push_back(AsyncValueFuture::InlineFragment2(async move {
let value = resolve_selection_set_into_async( let value = resolve_selection_set_into_async(
instance, instance,
info, info,

View file

@ -49,7 +49,6 @@ pub enum TypeKind {
/// ## Input objects /// ## Input objects
/// ///
/// Represents complex values provided in queries _into_ the system. /// Represents complex values provided in queries _into_ the system.
#[graphql(name = "INPUT_OBJECT")]
InputObject, InputObject,
/// ## List types /// ## List types
@ -63,7 +62,6 @@ pub enum TypeKind {
/// ///
/// In GraphQL, nullable types are the default. By putting a `!` after a\ /// In GraphQL, nullable types are the default. By putting a `!` after a\
/// type, it becomes non-nullable. /// type, it becomes non-nullable.
#[graphql(name = "NON_NULL")]
NonNull, NonNull,
} }
@ -89,7 +87,7 @@ impl<'a, S> Arguments<'a, S> {
if let (Some(args), Some(meta_args)) = (&mut args, meta_args) { if let (Some(args), Some(meta_args)) = (&mut args, meta_args) {
for arg in meta_args { for arg in meta_args {
let arg_name = arg.name.as_str(); let arg_name = arg.name.as_str();
if args.get(arg_name).map_or(true, InputValue::is_null) { if args.get(arg_name).is_none() {
if let Some(val) = arg.default_value.as_ref() { if let Some(val) = arg.default_value.as_ref() {
args.insert(arg_name, val.clone()); args.insert(arg_name, val.clone());
} }
@ -150,14 +148,14 @@ impl<'a, S> Arguments<'a, S> {
/// This trait is intended to be used in a conjunction with a [`GraphQLType`] trait. See the example /// This trait is intended to be used in a conjunction with a [`GraphQLType`] trait. See the example
/// in the documentation of a [`GraphQLType`] trait. /// in the documentation of a [`GraphQLType`] trait.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
/// [2]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Unions
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
/// [4]: https://spec.graphql.org/June2018/#sec-Scalars /// [4]: https://spec.graphql.org/October2021#sec-Scalars
/// [5]: https://spec.graphql.org/June2018/#sec-Enums /// [5]: https://spec.graphql.org/October2021#sec-Enums
/// [6]: https://spec.graphql.org/June2018/#sec-Type-System.List /// [6]: https://spec.graphql.org/October2021#sec-List
/// [7]: https://spec.graphql.org/June2018/#sec-Type-System.Non-Null /// [7]: https://spec.graphql.org/October2021#sec-Non-Null
/// [8]: https://spec.graphql.org/June2018/#sec-Input-Objects /// [8]: https://spec.graphql.org/October2021#sec-Input-Objects
/// [11]: https://doc.rust-lang.org/reference/items/traits.html#object-safety /// [11]: https://doc.rust-lang.org/reference/items/traits.html#object-safety
/// [12]: https://doc.rust-lang.org/reference/types/trait-object.html /// [12]: https://doc.rust-lang.org/reference/types/trait-object.html
pub trait GraphQLValue<S = DefaultScalarValue> pub trait GraphQLValue<S = DefaultScalarValue>
@ -196,7 +194,7 @@ where
/// ///
/// The default implementation panics. /// The default implementation panics.
/// ///
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve_field( fn resolve_field(
&self, &self,
_info: &Self::TypeInfo, _info: &Self::TypeInfo,
@ -217,9 +215,9 @@ where
/// ///
/// The default implementation panics. /// The default implementation panics.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
/// [2]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Unions
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve_into_type( fn resolve_into_type(
&self, &self,
info: &Self::TypeInfo, info: &Self::TypeInfo,
@ -243,9 +241,9 @@ where
/// ///
/// The default implementation panics. /// The default implementation panics.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
/// [2]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Unions
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
#[allow(unused_variables)] #[allow(unused_variables)]
fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String {
panic!( panic!(
@ -271,8 +269,8 @@ where
/// ///
/// The default implementation panics, if `selection_set` is [`None`]. /// The default implementation panics, if `selection_set` is [`None`].
/// ///
/// [0]: https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability /// [0]: https://spec.graphql.org/October2021#sec-Errors-and-Non-Nullability
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
fn resolve( fn resolve(
&self, &self,
info: &Self::TypeInfo, info: &Self::TypeInfo,
@ -379,13 +377,13 @@ where
/// // schema in `meta()` above, or a validation failed because of a this library bug. /// // schema in `meta()` above, or a validation failed because of a this library bug.
/// // /// //
/// // In either of those two cases, the only reasonable way out is to panic the thread. /// // In either of those two cases, the only reasonable way out is to panic the thread.
/// _ => panic!("Field {} not found on type User", field_name), /// _ => panic!("Field {field_name} not found on type User"),
/// } /// }
/// } /// }
/// } /// }
/// ``` /// ```
/// ///
/// [3]: https://spec.graphql.org/June2018/#sec-Objects /// [3]: https://spec.graphql.org/October2021#sec-Objects
pub trait GraphQLType<S = DefaultScalarValue>: GraphQLValue<S> pub trait GraphQLType<S = DefaultScalarValue>: GraphQLValue<S>
where where
S: ScalarValue, S: ScalarValue,
@ -454,7 +452,7 @@ where
panic!( panic!(
"Field {} not found on type {:?}", "Field {} not found on type {:?}",
f.name.item, f.name.item,
meta_type.name() meta_type.name(),
) )
}); });
@ -474,8 +472,8 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| { .filter_map(|&(ref k, ref v)| {
(k.item, v.item.clone().into_const(exec_vars)) v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
}) })
.collect() .collect()
}), }),
@ -608,7 +606,7 @@ where
.arguments .arguments
.iter() .iter()
.flat_map(|m| m.item.get("if")) .flat_map(|m| m.item.get("if"))
.flat_map(|v| v.item.clone().into_const(vars).convert()) .filter_map(|v| v.item.clone().into_const(vars)?.convert().ok())
.next() .next()
.unwrap(); .unwrap();

View file

@ -580,12 +580,11 @@ where
fn into_field_error(self) -> FieldError<S> { fn into_field_error(self) -> FieldError<S> {
const ERROR_PREFIX: &str = "Failed to convert into exact-size array"; const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
match self { match self {
Self::Null => format!("{}: Value cannot be `null`", ERROR_PREFIX).into(), Self::Null => format!("{ERROR_PREFIX}: Value cannot be `null`").into(),
Self::WrongCount { actual, expected } => format!( Self::WrongCount { actual, expected } => {
"{}: wrong elements count: {} instead of {}", format!("{ERROR_PREFIX}: wrong elements count: {actual} instead of {expected}",)
ERROR_PREFIX, actual, expected .into()
) }
.into(),
Self::Item(s) => s.into_field_error(), Self::Item(s) => s.into_field_error(),
} }
} }

View file

@ -17,23 +17,23 @@ use crate::{GraphQLType, ScalarValue};
/// [GraphQL objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5] /// [GraphQL objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5]
/// and [unions][6]) are not allowed. /// and [unions][6]) are not allowed.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars /// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/June2018/#sec-Enums /// [3]: https://spec.graphql.org/October2021#sec-Enums
/// [4]: https://spec.graphql.org/June2018/#sec-Interfaces /// [4]: https://spec.graphql.org/October2021#sec-Interfaces
/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects
/// [6]: https://spec.graphql.org/June2018/#sec-Unions /// [6]: https://spec.graphql.org/October2021#sec-Unions
pub trait GraphQLObject<S: ScalarValue>: GraphQLType<S> { pub trait GraphQLObject<S: ScalarValue>: GraphQLType<S> {
/// An arbitrary function without meaning. /// An arbitrary function without meaning.
/// ///
/// May contain compile timed check logic which ensures that types are used correctly according /// May contain compile timed check logic which ensures that types are used correctly according
/// to the [GraphQL specification][1]. /// to the [GraphQL specification][1].
/// ///
/// [1]: https://spec.graphql.org/June2018/ /// [1]: https://spec.graphql.org/October2021
fn mark() {} fn mark() {}
} }
impl<'a, S, T> GraphQLObject<S> for &T impl<S, T> GraphQLObject<S> for &T
where where
T: GraphQLObject<S> + ?Sized, T: GraphQLObject<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
@ -74,23 +74,23 @@ where
/// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] /// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5]
/// and [unions][6]) are not allowed. /// and [unions][6]) are not allowed.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sec-Interfaces
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars /// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/June2018/#sec-Enums /// [3]: https://spec.graphql.org/October2021#sec-Enums
/// [4]: https://spec.graphql.org/June2018/#sec-Objects /// [4]: https://spec.graphql.org/October2021#sec-Objects
/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects
/// [6]: https://spec.graphql.org/June2018/#sec-Unions /// [6]: https://spec.graphql.org/October2021#sec-Unions
pub trait GraphQLInterface<S: ScalarValue>: GraphQLType<S> { pub trait GraphQLInterface<S: ScalarValue>: GraphQLType<S> {
/// An arbitrary function without meaning. /// An arbitrary function without meaning.
/// ///
/// May contain compile timed check logic which ensures that types are used correctly according /// May contain compile timed check logic which ensures that types are used correctly according
/// to the [GraphQL specification][1]. /// to the [GraphQL specification][1].
/// ///
/// [1]: https://spec.graphql.org/June2018/ /// [1]: https://spec.graphql.org/October2021
fn mark() {} fn mark() {}
} }
impl<'a, S, T> GraphQLInterface<S> for &T impl<S, T> GraphQLInterface<S> for &T
where where
T: GraphQLInterface<S> + ?Sized, T: GraphQLInterface<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
@ -131,23 +131,23 @@ where
/// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and /// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and
/// [interfaces][6]) are not allowed. /// [interfaces][6]) are not allowed.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions /// [1]: https://spec.graphql.org/October2021#sec-Unions
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars /// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/June2018/#sec-Enums /// [3]: https://spec.graphql.org/October2021#sec-Enums
/// [4]: https://spec.graphql.org/June2018/#sec-Objects /// [4]: https://spec.graphql.org/October2021#sec-Objects
/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects
/// [6]: https://spec.graphql.org/June2018/#sec-Interfaces /// [6]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait GraphQLUnion<S: ScalarValue>: GraphQLType<S> { pub trait GraphQLUnion<S: ScalarValue>: GraphQLType<S> {
/// An arbitrary function without meaning. /// An arbitrary function without meaning.
/// ///
/// May contain compile timed check logic which ensures that types are used correctly according /// May contain compile timed check logic which ensures that types are used correctly according
/// to the [GraphQL specification][1]. /// to the [GraphQL specification][1].
/// ///
/// [1]: https://spec.graphql.org/June2018/ /// [1]: https://spec.graphql.org/October2021
fn mark() {} fn mark() {}
} }
impl<'a, S, T> GraphQLUnion<S> for &T impl<S, T> GraphQLUnion<S> for &T
where where
T: GraphQLUnion<S> + ?Sized, T: GraphQLUnion<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
@ -194,7 +194,7 @@ pub trait IsOutputType<S: ScalarValue>: GraphQLType<S> {
fn mark() {} fn mark() {}
} }
impl<'a, S, T> IsOutputType<S> for &T impl<S, T> IsOutputType<S> for &T
where where
T: IsOutputType<S> + ?Sized, T: IsOutputType<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
@ -282,7 +282,7 @@ where
} }
} }
impl<'a, S> IsOutputType<S> for str where S: ScalarValue {} impl<S> IsOutputType<S> for str where S: ScalarValue {}
/// Marker trait for types which can be used as input types. /// Marker trait for types which can be used as input types.
/// ///
@ -298,7 +298,7 @@ pub trait IsInputType<S: ScalarValue>: GraphQLType<S> {
fn mark() {} fn mark() {}
} }
impl<'a, S, T> IsInputType<S> for &T impl<S, T> IsInputType<S> for &T
where where
T: IsInputType<S> + ?Sized, T: IsInputType<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
@ -375,4 +375,4 @@ where
} }
} }
impl<'a, S> IsInputType<S> for str where S: ScalarValue {} impl<S> IsInputType<S> for str where S: ScalarValue {}

View file

@ -50,11 +50,10 @@ impl FromStr for Name {
type Err = NameParseError; type Err = NameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if Name::is_valid(s) { if Name::is_valid(s) {
Ok(Name(s.to_string())) Ok(Name(s.into()))
} else { } else {
Err(NameParseError(format!( Err(NameParseError(format!(
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{s}\" does not",
s
))) )))
} }
} }

View file

@ -32,12 +32,16 @@ use crate::{
pub enum Nullable<T> { pub enum Nullable<T> {
/// No value /// No value
ImplicitNull, ImplicitNull,
/// No value, explicitly specified to be null /// No value, explicitly specified to be null
ExplicitNull, ExplicitNull,
/// Some value `T` /// Some value `T`
Some(T), Some(T),
} }
// Implemented manually to omit redundant `T: Default` trait bound, imposed by
// `#[derive(Default)]`.
impl<T> Default for Nullable<T> { impl<T> Default for Nullable<T> {
fn default() -> Self { fn default() -> Self {
Self::ImplicitNull Self::ImplicitNull

View file

@ -265,7 +265,7 @@ where
} }
} }
impl<'e, S, T> GraphQLValueAsync<S> for Arc<T> impl<S, T> GraphQLValueAsync<S> for Arc<T>
where where
T: GraphQLValueAsync<S> + Send + ?Sized, T: GraphQLValueAsync<S> + Send + ?Sized,
T::TypeInfo: Sync, T::TypeInfo: Sync,

View file

@ -37,7 +37,7 @@ impl ID {
.map(str::to_owned) .map(str::to_owned)
.or_else(|| v.as_int_value().as_ref().map(ToString::to_string)) .or_else(|| v.as_int_value().as_ref().map(ToString::to_string))
.map(Self) .map(Self)
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
} }
} }
@ -81,10 +81,10 @@ mod impl_string_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<String, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<String, String> {
v.as_string_value() v.as_string_value()
.map(str::to_owned) .map(str::to_owned)
.ok_or_else(|| format!("Expected `String`, found: {}", v)) .ok_or_else(|| format!("Expected `String`, found: {v}"))
} }
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
if let ScalarToken::String(value) = value { if let ScalarToken::String(value) = value {
let mut ret = String::with_capacity(value.len()); let mut ret = String::with_capacity(value.len());
let mut char_iter = value.chars(); let mut char_iter = value.chars();
@ -120,7 +120,7 @@ mod impl_string_scalar {
} }
Some(s) => { Some(s) => {
return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence( return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence(
format!("\\{}", s), format!("\\{s}"),
))) )))
} }
None => return Err(ParseError::LexerError(LexerError::UnterminatedString)), None => return Err(ParseError::LexerError(LexerError::UnterminatedString)),
@ -132,12 +132,12 @@ mod impl_string_scalar {
} }
Ok(ret.into()) Ok(ret.into())
} else { } else {
Err(ParseError::UnexpectedToken(Token::Scalar(value))) Err(ParseError::unexpected_token(Token::Scalar(value)))
} }
} }
} }
fn parse_unicode_codepoint<'a, I>(char_iter: &mut I) -> Result<char, ParseError<'a>> fn parse_unicode_codepoint<I>(char_iter: &mut I) -> Result<char, ParseError>
where where
I: Iterator<Item = char>, I: Iterator<Item = char>,
{ {
@ -149,19 +149,16 @@ where
.and_then(|c1| { .and_then(|c1| {
char_iter char_iter
.next() .next()
.map(|c2| format!("{}{}", c1, c2)) .map(|c2| format!("{c1}{c2}"))
.ok_or_else(|| { .ok_or_else(|| {
ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{}", c1))) ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{c1}")))
}) })
}) })
.and_then(|mut s| { .and_then(|mut s| {
char_iter char_iter
.next() .next()
.ok_or_else(|| { .ok_or_else(|| {
ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}")))
"\\u{}",
s.clone()
)))
}) })
.map(|c2| { .map(|c2| {
s.push(c2); s.push(c2);
@ -172,10 +169,7 @@ where
char_iter char_iter
.next() .next()
.ok_or_else(|| { .ok_or_else(|| {
ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}")))
"\\u{}",
s.clone()
)))
}) })
.map(|c2| { .map(|c2| {
s.push(c2); s.push(c2);
@ -184,14 +178,12 @@ where
})?; })?;
let code_point = u32::from_str_radix(&escaped_code_point, 16).map_err(|_| { let code_point = u32::from_str_radix(&escaped_code_point, 16).map_err(|_| {
ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( ParseError::LexerError(LexerError::UnknownEscapeSequence(format!(
"\\u{}", "\\u{escaped_code_point}",
escaped_code_point
))) )))
})?; })?;
char::from_u32(code_point).ok_or_else(|| { char::from_u32(code_point).ok_or_else(|| {
ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( ParseError::LexerError(LexerError::UnknownEscapeSequence(format!(
"\\u{}", "\\u{escaped_code_point}",
escaped_code_point
))) )))
}) })
} }
@ -282,12 +274,12 @@ mod impl_boolean_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, String> {
v.as_scalar_value() v.as_scalar_value()
.and_then(ScalarValue::as_bool) .and_then(ScalarValue::as_bool)
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) .ok_or_else(|| format!("Expected `Boolean`, found: {v}"))
} }
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
// `Boolean`s are parsed separately, they shouldn't reach this code path. // `Boolean`s are parsed separately, they shouldn't reach this code path.
Err(ParseError::UnexpectedToken(Token::Scalar(value))) Err(ParseError::unexpected_token(Token::Scalar(value)))
} }
} }
@ -303,16 +295,16 @@ mod impl_int_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Int, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Int, String> {
v.as_int_value() v.as_int_value()
.ok_or_else(|| format!("Expected `Int`, found: {}", v)) .ok_or_else(|| format!("Expected `Int`, found: {v}"))
} }
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
if let ScalarToken::Int(v) = value { if let ScalarToken::Int(v) = value {
v.parse() v.parse()
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) .map_err(|_| ParseError::unexpected_token(Token::Scalar(value)))
.map(|s: i32| s.into()) .map(|s: i32| s.into())
} else { } else {
Err(ParseError::UnexpectedToken(Token::Scalar(value))) Err(ParseError::unexpected_token(Token::Scalar(value)))
} }
} }
} }
@ -329,20 +321,20 @@ mod impl_float_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Float, String> { pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Float, String> {
v.as_float_value() v.as_float_value()
.ok_or_else(|| format!("Expected `Float`, found: {}", v)) .ok_or_else(|| format!("Expected `Float`, found: {v}"))
} }
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
match value { match value {
ScalarToken::Int(v) => v ScalarToken::Int(v) => v
.parse() .parse()
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) .map_err(|_| ParseError::unexpected_token(Token::Scalar(value)))
.map(|s: i32| f64::from(s).into()), .map(|s: i32| f64::from(s).into()),
ScalarToken::Float(v) => v ScalarToken::Float(v) => v
.parse() .parse()
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) .map_err(|_| ParseError::unexpected_token(Token::Scalar(value)))
.map(|s: f64| s.into()), .map(|s: f64| s.into()),
ScalarToken::String(_) => Err(ParseError::UnexpectedToken(Token::Scalar(value))), ScalarToken::String(_) => Err(ParseError::unexpected_token(Token::Scalar(value))),
} }
} }
} }
@ -401,8 +393,9 @@ where
{ {
} }
// Implemented manually to omit redundant `T: Default` trait bound, imposed by
// `#[derive(Default)]`.
impl<T> Default for EmptyMutation<T> { impl<T> Default for EmptyMutation<T> {
#[inline]
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@ -461,8 +454,9 @@ where
{ {
} }
// Implemented manually to omit redundant `T: Default` trait bound, imposed by
// `#[derive(Default)]`.
impl<T> Default for EmptySubscription<T> { impl<T> Default for EmptySubscription<T> {
#[inline]
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@ -499,8 +493,8 @@ mod tests {
#[test] #[test]
fn test_id_display() { fn test_id_display() {
let id = ID(String::from("foo")); let id = ID("foo".into());
assert_eq!(format!("{}", id), "foo"); assert_eq!(id.to_string(), "foo");
} }
#[test] #[test]
@ -508,7 +502,7 @@ mod tests {
fn parse_string(s: &str, expected: &str) { fn parse_string(s: &str, expected: &str) {
let s = let s =
<String as ParseScalarValue<DefaultScalarValue>>::from_str(ScalarToken::String(s)); <String as ParseScalarValue<DefaultScalarValue>>::from_str(ScalarToken::String(s));
assert!(s.is_ok(), "A parsing error occurred: {:?}", s); assert!(s.is_ok(), "A parsing error occurred: {s:?}");
let s: Option<String> = s.unwrap().into(); let s: Option<String> = s.unwrap().into();
assert!(s.is_some(), "No string returned"); assert!(s.is_some(), "No string returned");
assert_eq!(s.unwrap(), expected); assert_eq!(s.unwrap(), expected);
@ -527,7 +521,7 @@ mod tests {
#[test] #[test]
fn parse_f64_from_int() { fn parse_f64_from_int() {
for (v, expected) in &[ for (v, expected) in [
("0", 0), ("0", 0),
("128", 128), ("128", 128),
("1601942400", 1601942400), ("1601942400", 1601942400),
@ -538,14 +532,14 @@ mod tests {
assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err());
let n: Option<f64> = n.unwrap().into(); let n: Option<f64> = n.unwrap().into();
assert!(n.is_some(), "No f64 returned"); assert!(n.is_some(), "No `f64` returned");
assert_eq!(n.unwrap(), f64::from(*expected)); assert_eq!(n.unwrap(), f64::from(expected));
} }
} }
#[test] #[test]
fn parse_f64_from_float() { fn parse_f64_from_float() {
for (v, expected) in &[ for (v, expected) in [
("0.", 0.), ("0.", 0.),
("1.2", 1.2), ("1.2", 1.2),
("1601942400.", 1601942400.), ("1601942400.", 1601942400.),
@ -556,8 +550,8 @@ mod tests {
assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err());
let n: Option<f64> = n.unwrap().into(); let n: Option<f64> = n.unwrap().into();
assert!(n.is_some(), "No f64 returned"); assert!(n.is_some(), "No `f64` returned");
assert_eq!(n.unwrap(), *expected); assert_eq!(n.unwrap(), expected);
} }
} }

View file

@ -94,8 +94,8 @@ pub trait SubscriptionConnection<S>: futures::Stream<Item = ExecutionOutput<S>>
/// ///
/// See trait methods for more detailed explanation on how this trait works. /// See trait methods for more detailed explanation on how this trait works.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Subscription /// [1]: https://spec.graphql.org/October2021#sec-Subscription
/// [2]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Objects
pub trait GraphQLSubscriptionValue<S = DefaultScalarValue>: GraphQLValue<S> + Sync pub trait GraphQLSubscriptionValue<S = DefaultScalarValue>: GraphQLValue<S> + Sync
where where
Self::TypeInfo: Sync, Self::TypeInfo: Sync,
@ -204,7 +204,7 @@ crate::sa::assert_obj_safe!(GraphQLSubscriptionValue<Context = (), TypeInfo = ()
/// It's automatically implemented for [`GraphQLSubscriptionValue`] and [`GraphQLType`] /// It's automatically implemented for [`GraphQLSubscriptionValue`] and [`GraphQLType`]
/// implementers, so doesn't require manual or code-generated implementation. /// implementers, so doesn't require manual or code-generated implementation.
/// ///
/// [1]: https://spec.graphql.org/June2018/#sec-Subscription /// [1]: https://spec.graphql.org/October2021#sec-Subscription
pub trait GraphQLSubscriptionType<S = DefaultScalarValue>: pub trait GraphQLSubscriptionType<S = DefaultScalarValue>:
GraphQLSubscriptionValue<S> + GraphQLType<S> GraphQLSubscriptionValue<S> + GraphQLType<S>
where where
@ -316,7 +316,9 @@ where
f.arguments.as_ref().map(|m| { f.arguments.as_ref().map(|m| {
m.item m.item
.iter() .iter()
.map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) .filter_map(|&(ref k, ref v)| {
v.item.clone().into_const(exec_vars).map(|v| (k.item, v))
})
.collect() .collect()
}), }),
&meta_field.arguments, &meta_field.arguments,

View file

@ -25,6 +25,7 @@ where
} }
} }
TypeType::List(ref inner, expected_size) => match *arg_value { TypeType::List(ref inner, expected_size) => match *arg_value {
InputValue::Null | InputValue::Variable(_) => true,
InputValue::List(ref items) => { InputValue::List(ref items) => {
if let Some(expected) = expected_size { if let Some(expected) = expected_size {
if items.len() != expected { if items.len() != expected {
@ -71,7 +72,7 @@ where
let mut remaining_required_fields = input_fields let mut remaining_required_fields = input_fields
.iter() .iter()
.filter_map(|f| { .filter_map(|f| {
if f.arg_type.is_non_null() { if f.arg_type.is_non_null() && f.default_value.is_none() {
Some(&f.name) Some(&f.name)
} else { } else {
None None

View file

@ -30,9 +30,9 @@ pub struct ValidatorContext<'a, S: Debug + 'a> {
impl RuleError { impl RuleError {
#[doc(hidden)] #[doc(hidden)]
pub fn new(message: &str, locations: &[SourcePosition]) -> RuleError { pub fn new(message: &str, locations: &[SourcePosition]) -> Self {
RuleError { Self {
message: message.to_owned(), message: message.into(),
locations: locations.to_vec(), locations: locations.to_vec(),
} }
} }
@ -53,14 +53,15 @@ impl RuleError {
impl fmt::Display for RuleError { impl fmt::Display for RuleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// this is fine since all `RuleError`s should have at least one source position // This is fine since all `RuleError`s should have at least one source
// position.
let locations = self let locations = self
.locations .locations
.iter() .iter()
.map(|location| format!("{}", location)) .map(ToString::to_string)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
write!(f, "{}. At {}", self.message, locations) write!(f, "{}. At {locations}", self.message)
} }
} }

View file

@ -99,7 +99,7 @@ where
var_name, var_name,
var_pos, var_pos,
&path, &path,
&format!(r#"Expected "{}", found null"#, meta_type), format!(r#"Expected "{meta_type}", found null"#),
)); ));
} else { } else {
errors.append(&mut unify_value( errors.append(&mut unify_value(
@ -121,10 +121,10 @@ where
var_name, var_name,
var_pos, var_pos,
&path, &path,
&format!( format!(
"Expected list of {} elements, found {} elements", "Expected list of {expected} elements, \
expected, found {} elements",
l.len() l.len(),
), ),
)); ));
} }
@ -168,11 +168,11 @@ where
var_name, var_name,
var_pos, var_pos,
&path, &path,
&format!( format!(
"Expected input of type `{}`. Got: `{}`. \ "Expected input of type `{}`. \
Got: `{value}`. \
Details: {}", Details: {}",
iom.name, iom.name,
value,
e.message(), e.message(),
), ),
)); ));
@ -205,10 +205,9 @@ where
var_name, var_name,
var_pos, var_pos,
path, path,
&format!( format!(
"Expected input scalar `{}`. Got: `{}`. Details: {}", "Expected input scalar `{}`. Got: `{value}`. Details: {}",
meta.name, meta.name,
value,
e.message(), e.message(),
), ),
)]; )];
@ -219,13 +218,13 @@ where
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Expected "{}", found list"#, meta.name), format!(r#"Expected "{}", found list"#, meta.name),
)), )),
InputValue::Object(_) => errors.push(unification_error( InputValue::Object(_) => errors.push(unification_error(
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Expected "{}", found object"#, meta.name), format!(r#"Expected "{}", found object"#, meta.name),
)), )),
_ => (), _ => (),
} }
@ -244,27 +243,27 @@ where
{ {
let mut errors: Vec<RuleError> = vec![]; let mut errors: Vec<RuleError> = vec![];
match *value { match value {
// TODO: avoid this bad duplicate as_str() call. (value system refactor) // TODO: avoid this bad duplicate as_str() call. (value system refactor)
InputValue::Scalar(ref scalar) if scalar.as_str().is_some() => { InputValue::Scalar(scalar) if scalar.as_str().is_some() => {
if let Some(name) = scalar.as_str() { if let Some(name) = scalar.as_str() {
if !meta.values.iter().any(|ev| ev.name == *name) { if !meta.values.iter().any(|ev| ev.name == *name) {
errors.push(unification_error( errors.push(unification_error(
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Invalid value for enum "{}""#, meta.name), format!(r#"Invalid value for enum "{}""#, meta.name),
)) ))
} }
} }
} }
InputValue::Enum(ref name) => { InputValue::Enum(name) => {
if !meta.values.iter().any(|ev| &ev.name == name) { if !meta.values.iter().any(|ev| &ev.name == name) {
errors.push(unification_error( errors.push(unification_error(
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Invalid value for enum "{}""#, meta.name), format!(r#"Invalid value for enum "{}""#, meta.name),
)) ))
} }
} }
@ -272,7 +271,7 @@ where
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Expected "{}", found not a string or enum"#, meta.name), format!(r#"Expected "{}", found not a string or enum"#, meta.name),
)), )),
} }
errors errors
@ -318,7 +317,7 @@ where
var_name, var_name,
var_pos, var_pos,
&Path::ObjectField(&input_field.name, path), &Path::ObjectField(&input_field.name, path),
&format!(r#"Expected "{}", found null"#, input_field.arg_type), format!(r#"Expected "{}", found null"#, input_field.arg_type),
)); ));
} }
} }
@ -336,7 +335,7 @@ where
var_name, var_name,
var_pos, var_pos,
path, path,
&format!(r#"Expected "{}", found not an object"#, meta.name), format!(r#"Expected "{}", found not an object"#, meta.name),
)); ));
} }
errors errors
@ -349,17 +348,14 @@ where
v.map_or(true, InputValue::is_null) v.map_or(true, InputValue::is_null)
} }
fn unification_error<'a>( fn unification_error(
var_name: &str, var_name: impl fmt::Display,
var_pos: &SourcePosition, var_pos: &SourcePosition,
path: &Path<'a>, path: &Path<'_>,
message: &str, message: impl fmt::Display,
) -> RuleError { ) -> RuleError {
RuleError::new( RuleError::new(
&format!( &format!(r#"Variable "${var_name}" got invalid value. {path}{message}."#),
r#"Variable "${}" got invalid value. {}{}."#,
var_name, path, message,
),
&[*var_pos], &[*var_pos],
) )
} }
@ -368,8 +364,8 @@ impl<'a> fmt::Display for Path<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Path::Root => write!(f, ""), Path::Root => write!(f, ""),
Path::ArrayElement(idx, prev) => write!(f, "{}In element #{}: ", prev, idx), Path::ArrayElement(idx, prev) => write!(f, "{prev}In element #{idx}: "),
Path::ObjectField(name, prev) => write!(f, r#"{}In field "{}": "#, prev, name), Path::ObjectField(name, prev) => write!(f, r#"{prev}In field "{name}": "#),
} }
} }
} }

View file

@ -1,3 +1,5 @@
use std::fmt;
use crate::{ use crate::{
ast::{Directive, Field, InputValue}, ast::{Directive, Field, InputValue},
parser::Spanning, parser::Spanning,
@ -6,13 +8,12 @@ use crate::{
validation::{ValidatorContext, Visitor}, validation::{ValidatorContext, Visitor},
value::ScalarValue, value::ScalarValue,
}; };
use std::fmt::Debug;
pub struct ArgumentsOfCorrectType<'a, S: Debug + 'a> { pub struct ArgumentsOfCorrectType<'a, S: fmt::Debug + 'a> {
current_args: Option<&'a Vec<Argument<'a, S>>>, current_args: Option<&'a Vec<Argument<'a, S>>>,
} }
pub fn factory<'a, S: Debug>() -> ArgumentsOfCorrectType<'a, S> { pub fn factory<'a, S: fmt::Debug>() -> ArgumentsOfCorrectType<'a, S> {
ArgumentsOfCorrectType { current_args: None } ArgumentsOfCorrectType { current_args: None }
} }
@ -59,7 +60,7 @@ where
if !is_valid_literal_value(ctx.schema, &meta_type, &arg_value.item) { if !is_valid_literal_value(ctx.schema, &meta_type, &arg_value.item) {
ctx.report_error( ctx.report_error(
&error_message(arg_name.item, &format!("{}", argument_meta.arg_type)), &error_message(arg_name.item, &argument_meta.arg_type),
&[arg_value.start], &[arg_value.start],
); );
} }
@ -67,11 +68,8 @@ where
} }
} }
fn error_message(arg_name: &str, type_name: &str) -> String { fn error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
format!( format!("Invalid value for argument \"{arg_name}\", expected type \"{type_name}\"",)
"Invalid value for argument \"{}\", expected type \"{}\"",
arg_name, type_name
)
} }
#[cfg(test)] #[cfg(test)]
@ -85,7 +83,7 @@ mod tests {
}; };
#[test] #[test]
fn good_null_value() { fn null_into_nullable_int() {
expect_passes_rule::<_, _, DefaultScalarValue>( expect_passes_rule::<_, _, DefaultScalarValue>(
factory, factory,
r#" r#"
@ -98,6 +96,20 @@ mod tests {
); );
} }
#[test]
fn null_into_nullable_list() {
expect_passes_rule::<_, _, DefaultScalarValue>(
factory,
r#"
{
complicatedArgs {
stringListArgField(stringListArg: null)
}
}
"#,
);
}
#[test] #[test]
fn null_into_int() { fn null_into_int() {
expect_fails_rule::<_, _, DefaultScalarValue>( expect_fails_rule::<_, _, DefaultScalarValue>(
@ -116,6 +128,24 @@ mod tests {
); );
} }
#[test]
fn null_into_list() {
expect_fails_rule::<_, _, DefaultScalarValue>(
factory,
r#"
{
complicatedArgs {
nonNullStringListArgField(nonNullStringListArg: null)
}
}
"#,
&[RuleError::new(
&error_message("nonNullStringListArg", "[String!]!"),
&[SourcePosition::new(111, 3, 64)],
)],
);
}
#[test] #[test]
fn good_int_value() { fn good_int_value() {
expect_passes_rule::<_, _, DefaultScalarValue>( expect_passes_rule::<_, _, DefaultScalarValue>(

View file

@ -1,3 +1,5 @@
use std::fmt;
use crate::{ use crate::{
ast::VariableDefinition, ast::VariableDefinition,
parser::Spanning, parser::Spanning,
@ -29,7 +31,7 @@ where
{ {
if var_def.var_type.item.is_non_null() { if var_def.var_type.item.is_non_null() {
ctx.report_error( ctx.report_error(
&non_null_error_message(var_name.item, &format!("{}", var_def.var_type.item)), &non_null_error_message(var_name.item, &var_def.var_type.item),
&[*start], &[*start],
) )
} else { } else {
@ -37,7 +39,7 @@ where
if !is_valid_literal_value(ctx.schema, &meta_type, var_value) { if !is_valid_literal_value(ctx.schema, &meta_type, var_value) {
ctx.report_error( ctx.report_error(
&type_error_message(var_name.item, &format!("{}", var_def.var_type.item)), &type_error_message(var_name.item, &var_def.var_type.item),
&[*start], &[*start],
); );
} }
@ -46,17 +48,14 @@ where
} }
} }
fn type_error_message(arg_name: &str, type_name: &str) -> String { fn type_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
format!( format!("Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\"")
"Invalid default value for argument \"{}\", expected type \"{}\"",
arg_name, type_name
)
} }
fn non_null_error_message(arg_name: &str, type_name: &str) -> String { fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
format!( format!(
"Argument \"{}\" has type \"{}\" and is not nullable, so it can't have a default value", "Argument \"{arg_name}\" has type \"{type_name}\" and is not nullable, \
arg_name, type_name so it can't have a default value",
) )
} }

View file

@ -69,7 +69,7 @@ where
} }
fn error_message(field: &str, type_name: &str) -> String { fn error_message(field: &str, type_name: &str) -> String {
format!(r#"Unknown field "{}" on type "{}""#, field, type_name) format!(r#"Unknown field "{field}" on type "{type_name}""#)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -59,15 +59,9 @@ where
fn error_message(fragment_name: Option<&str>, on_type: &str) -> String { fn error_message(fragment_name: Option<&str>, on_type: &str) -> String {
if let Some(name) = fragment_name { if let Some(name) = fragment_name {
format!( format!(r#"Fragment "{name}" cannot condition non composite type "{on_type}"#)
r#"Fragment "{}" cannot condition non composite type "{}"#,
name, on_type
)
} else { } else {
format!( format!(r#"Fragment cannot condition on non composite type "{on_type}""#)
r#"Fragment cannot condition on non composite type "{}""#,
on_type
)
} }
} }

View file

@ -91,17 +91,11 @@ where
} }
fn field_error_message(arg_name: &str, field_name: &str, type_name: &str) -> String { fn field_error_message(arg_name: &str, field_name: &str, type_name: &str) -> String {
format!( format!(r#"Unknown argument "{arg_name}" on field "{field_name}" of type "{type_name}""#)
r#"Unknown argument "{}" on field "{}" of type "{}""#,
arg_name, field_name, type_name
)
} }
fn directive_error_message(arg_name: &str, directive_name: &str) -> String { fn directive_error_message(arg_name: &str, directive_name: &str) -> String {
format!( format!(r#"Unknown argument "{arg_name}" on directive "{directive_name}""#)
r#"Unknown argument "{}" on directive "{}""#,
arg_name, directive_name
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -154,14 +154,11 @@ where
} }
fn unknown_error_message(directive_name: &str) -> String { fn unknown_error_message(directive_name: &str) -> String {
format!(r#"Unknown directive "{}""#, directive_name) format!(r#"Unknown directive "{directive_name}""#)
} }
fn misplaced_error_message(directive_name: &str, location: &DirectiveLocation) -> String { fn misplaced_error_message(directive_name: &str, location: &DirectiveLocation) -> String {
format!( format!(r#"Directive "{directive_name}" may not be used on {location}"#)
r#"Directive "{}" may not be used on {}"#,
directive_name, location
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -28,7 +28,7 @@ where
} }
fn error_message(frag_name: &str) -> String { fn error_message(frag_name: &str) -> String {
format!(r#"Unknown fragment: "{}""#, frag_name) format!(r#"Unknown fragment: "{frag_name}""#)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -56,7 +56,7 @@ fn validate_type<'a, S: Debug>(
} }
fn error_message(type_name: &str) -> String { fn error_message(type_name: &str) -> String {
format!(r#"Unknown type "{}""#, type_name) format!(r#"Unknown type "{type_name}""#)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -7,19 +7,6 @@ use crate::{
value::ScalarValue, value::ScalarValue,
}; };
pub struct NoFragmentCycles<'a> {
current_fragment: Option<&'a str>,
spreads: HashMap<&'a str, Vec<Spanning<&'a str>>>,
fragment_order: Vec<&'a str>,
}
struct CycleDetector<'a> {
visited: HashSet<&'a str>,
spreads: &'a HashMap<&'a str, Vec<Spanning<&'a str>>>,
path_indices: HashMap<&'a str, usize>,
errors: Vec<RuleError>,
}
pub fn factory<'a>() -> NoFragmentCycles<'a> { pub fn factory<'a>() -> NoFragmentCycles<'a> {
NoFragmentCycles { NoFragmentCycles {
current_fragment: None, current_fragment: None,
@ -28,6 +15,12 @@ pub fn factory<'a>() -> NoFragmentCycles<'a> {
} }
} }
pub struct NoFragmentCycles<'a> {
current_fragment: Option<&'a str>,
spreads: HashMap<&'a str, Vec<Spanning<&'a str>>>,
fragment_order: Vec<&'a str>,
}
impl<'a, S> Visitor<'a, S> for NoFragmentCycles<'a> impl<'a, S> Visitor<'a, S> for NoFragmentCycles<'a>
where where
S: ScalarValue, S: ScalarValue,
@ -38,14 +31,12 @@ where
let mut detector = CycleDetector { let mut detector = CycleDetector {
visited: HashSet::new(), visited: HashSet::new(),
spreads: &self.spreads, spreads: &self.spreads,
path_indices: HashMap::new(),
errors: Vec::new(), errors: Vec::new(),
}; };
for frag in &self.fragment_order { for frag in &self.fragment_order {
if !detector.visited.contains(frag) { if !detector.visited.contains(frag) {
let mut path = Vec::new(); detector.detect_from(frag);
detector.detect_from(frag, &mut path);
} }
} }
@ -91,19 +82,46 @@ where
} }
} }
type CycleDetectorState<'a> = (&'a str, Vec<&'a Spanning<&'a str>>, HashMap<&'a str, usize>);
struct CycleDetector<'a> {
visited: HashSet<&'a str>,
spreads: &'a HashMap<&'a str, Vec<Spanning<&'a str>>>,
errors: Vec<RuleError>,
}
impl<'a> CycleDetector<'a> { impl<'a> CycleDetector<'a> {
fn detect_from(&mut self, from: &'a str, path: &mut Vec<&'a Spanning<&'a str>>) { fn detect_from(&mut self, from: &'a str) {
let mut to_visit = Vec::new();
to_visit.push((from, Vec::new(), HashMap::new()));
while let Some((from, path, path_indices)) = to_visit.pop() {
to_visit.extend(self.detect_from_inner(from, path, path_indices));
}
}
/// This function should be called only inside [`Self::detect_from()`], as
/// it's a recursive function using heap instead of a stack. So, instead of
/// the recursive call, we return a [`Vec`] that is visited inside
/// [`Self::detect_from()`].
fn detect_from_inner(
&mut self,
from: &'a str,
path: Vec<&'a Spanning<&'a str>>,
mut path_indices: HashMap<&'a str, usize>,
) -> Vec<CycleDetectorState<'a>> {
self.visited.insert(from); self.visited.insert(from);
if !self.spreads.contains_key(from) { if !self.spreads.contains_key(from) {
return; return Vec::new();
} }
self.path_indices.insert(from, path.len()); path_indices.insert(from, path.len());
let mut to_visit = Vec::new();
for node in &self.spreads[from] { for node in &self.spreads[from] {
let name = &node.item; let name = node.item;
let index = self.path_indices.get(name).cloned(); let index = path_indices.get(name).cloned();
if let Some(index) = index { if let Some(index) = index {
let err_pos = if index < path.len() { let err_pos = if index < path.len() {
@ -114,19 +132,19 @@ impl<'a> CycleDetector<'a> {
self.errors self.errors
.push(RuleError::new(&error_message(name), &[err_pos.start])); .push(RuleError::new(&error_message(name), &[err_pos.start]));
} else if !self.visited.contains(name) { } else {
let mut path = path.clone();
path.push(node); path.push(node);
self.detect_from(name, path); to_visit.push((name, path, path_indices.clone()));
path.pop();
} }
} }
self.path_indices.remove(from); to_visit
} }
} }
fn error_message(frag_name: &str) -> String { fn error_message(frag_name: &str) -> String {
format!(r#"Cannot spread fragment "{}""#, frag_name) format!(r#"Cannot spread fragment "{frag_name}""#)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -12,13 +12,6 @@ pub enum Scope<'a> {
Fragment(&'a str), Fragment(&'a str),
} }
pub struct NoUndefinedVariables<'a> {
defined_variables: HashMap<Option<&'a str>, (SourcePosition, HashSet<&'a str>)>,
used_variables: HashMap<Scope<'a>, Vec<Spanning<&'a str>>>,
current_scope: Option<Scope<'a>>,
spreads: HashMap<Scope<'a>, Vec<&'a str>>,
}
pub fn factory<'a>() -> NoUndefinedVariables<'a> { pub fn factory<'a>() -> NoUndefinedVariables<'a> {
NoUndefinedVariables { NoUndefinedVariables {
defined_variables: HashMap::new(), defined_variables: HashMap::new(),
@ -28,6 +21,13 @@ pub fn factory<'a>() -> NoUndefinedVariables<'a> {
} }
} }
pub struct NoUndefinedVariables<'a> {
defined_variables: HashMap<Option<&'a str>, (SourcePosition, HashSet<&'a str>)>,
used_variables: HashMap<Scope<'a>, Vec<Spanning<&'a str>>>,
current_scope: Option<Scope<'a>>,
spreads: HashMap<Scope<'a>, Vec<&'a str>>,
}
impl<'a> NoUndefinedVariables<'a> { impl<'a> NoUndefinedVariables<'a> {
fn find_undef_vars( fn find_undef_vars(
&'a self, &'a self,
@ -36,8 +36,34 @@ impl<'a> NoUndefinedVariables<'a> {
unused: &mut Vec<&'a Spanning<&'a str>>, unused: &mut Vec<&'a Spanning<&'a str>>,
visited: &mut HashSet<Scope<'a>>, visited: &mut HashSet<Scope<'a>>,
) { ) {
let mut to_visit = Vec::new();
if let Some(spreads) = self.find_undef_vars_inner(scope, defined, unused, visited) {
to_visit.push(spreads);
}
while let Some(spreads) = to_visit.pop() {
for spread in spreads {
if let Some(spreads) =
self.find_undef_vars_inner(&Scope::Fragment(spread), defined, unused, visited)
{
to_visit.push(spreads);
}
}
}
}
/// This function should be called only inside [`Self::find_undef_vars()`],
/// as it's a recursive function using heap instead of a stack. So, instead
/// of the recursive call, we return a [`Vec`] that is visited inside
/// [`Self::find_undef_vars()`].
fn find_undef_vars_inner(
&'a self,
scope: &Scope<'a>,
defined: &HashSet<&'a str>,
unused: &mut Vec<&'a Spanning<&'a str>>,
visited: &mut HashSet<Scope<'a>>,
) -> Option<&'a Vec<&'a str>> {
if visited.contains(scope) { if visited.contains(scope) {
return; return None;
} }
visited.insert(scope.clone()); visited.insert(scope.clone());
@ -50,11 +76,7 @@ impl<'a> NoUndefinedVariables<'a> {
} }
} }
if let Some(spreads) = self.spreads.get(scope) { self.spreads.get(scope)
for spread in spreads {
self.find_undef_vars(&Scope::Fragment(spread), defined, unused, visited);
}
}
} }
} }
@ -151,12 +173,9 @@ where
fn error_message(var_name: &str, op_name: Option<&str>) -> String { fn error_message(var_name: &str, op_name: Option<&str>) -> String {
if let Some(op_name) = op_name { if let Some(op_name) = op_name {
format!( format!(r#"Variable "${var_name}" is not defined by operation "{op_name}""#)
r#"Variable "${}" is not defined by operation "{}""#,
var_name, op_name
)
} else { } else {
format!(r#"Variable "${}" is not defined"#, var_name) format!(r#"Variable "${var_name}" is not defined"#)
} }
} }

View file

@ -7,18 +7,12 @@ use crate::{
value::ScalarValue, value::ScalarValue,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Scope<'a> { pub enum Scope<'a> {
Operation(Option<&'a str>), Operation(Option<&'a str>),
Fragment(&'a str), Fragment(&'a str),
} }
pub struct NoUnusedFragments<'a> {
spreads: HashMap<Scope<'a>, Vec<&'a str>>,
defined_fragments: HashSet<Spanning<&'a str>>,
current_scope: Option<Scope<'a>>,
}
pub fn factory<'a>() -> NoUnusedFragments<'a> { pub fn factory<'a>() -> NoUnusedFragments<'a> {
NoUnusedFragments { NoUnusedFragments {
spreads: HashMap::new(), spreads: HashMap::new(),
@ -27,21 +21,42 @@ pub fn factory<'a>() -> NoUnusedFragments<'a> {
} }
} }
pub struct NoUnusedFragments<'a> {
spreads: HashMap<Scope<'a>, Vec<&'a str>>,
defined_fragments: HashSet<Spanning<&'a str>>,
current_scope: Option<Scope<'a>>,
}
impl<'a> NoUnusedFragments<'a> { impl<'a> NoUnusedFragments<'a> {
fn find_reachable_fragments(&self, from: &Scope<'a>, result: &mut HashSet<&'a str>) { fn find_reachable_fragments(&'a self, from: Scope<'a>, result: &mut HashSet<&'a str>) {
if let Scope::Fragment(name) = *from { let mut to_visit = Vec::new();
to_visit.push(from);
while let Some(from) = to_visit.pop() {
if let Some(next) = self.find_reachable_fragments_inner(from, result) {
to_visit.extend(next.iter().map(|s| Scope::Fragment(s)));
}
}
}
/// This function should be called only inside
/// [`Self::find_reachable_fragments()`], as it's a recursive function using
/// heap instead of a stack. So, instead of the recursive call, we return a
/// [`Vec`] that is visited inside [`Self::find_reachable_fragments()`].
fn find_reachable_fragments_inner(
&'a self,
from: Scope<'a>,
result: &mut HashSet<&'a str>,
) -> Option<&'a Vec<&'a str>> {
if let Scope::Fragment(name) = from {
if result.contains(name) { if result.contains(name) {
return; return None;
} else { } else {
result.insert(name); result.insert(name);
} }
} }
if let Some(spreads) = self.spreads.get(from) { self.spreads.get(&from)
for spread in spreads {
self.find_reachable_fragments(&Scope::Fragment(spread), result)
}
}
} }
} }
@ -59,7 +74,7 @@ where
}) = *def }) = *def
{ {
let op_name = name.as_ref().map(|s| s.item); let op_name = name.as_ref().map(|s| s.item);
self.find_reachable_fragments(&Scope::Operation(op_name), &mut reachable); self.find_reachable_fragments(Scope::Operation(op_name), &mut reachable);
} }
} }
@ -96,7 +111,7 @@ where
) { ) {
if let Some(ref scope) = self.current_scope { if let Some(ref scope) = self.current_scope {
self.spreads self.spreads
.entry(scope.clone()) .entry(*scope)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(spread.item.name.item); .push(spread.item.name.item);
} }
@ -104,7 +119,7 @@ where
} }
fn error_message(frag_name: &str) -> String { fn error_message(frag_name: &str) -> String {
format!(r#"Fragment "{}" is never used"#, frag_name) format!(r#"Fragment "{frag_name}" is never used"#)
} }
#[cfg(test)] #[cfg(test)]

Some files were not shown because too many files have changed in this diff Show more