juniper-0.16.2/.cargo_vcs_info.json0000644000000001450000000000100125760ustar { "git": { "sha1": "624c2d8e41946b074c83982cdb159e7a741d6039" }, "path_in_vcs": "juniper" }juniper-0.16.2/CHANGELOG.md000064400000000000000000000263441046102023000132100ustar 00000000000000`juniper` changelog =================== All user visible changes to `juniper` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. ## [0.16.2] · 2025-06-25 [0.16.2]: /../../tree/juniper-v0.16.2/juniper [Diff](/../../compare/juniper-v0.16.1...juniper-v0.16.2) | [Milestone](/../../milestone/8) ### Fixed - Non-pinned versions of [GraphiQL]-related libraries in HTML page returned by `graphiql_source()`. ([ed2ef133], [#1332]) [#1332]: /../../issues/1332 [ed2ef133]: /../../commit/ed2ef13358a84bf9cc43835d4495b2b3395e7392 ## [0.16.1] · 2024-04-04 [0.16.1]: /../../tree/juniper-v0.16.1/juniper [Diff](/../../compare/juniper-v0.16.0...juniper-v0.16.1) | [Milestone](/../../milestone/6) ### Changed - Updated [GraphiQL] to 3.1.2 version. ([#1251]) [#1251]: /../../pull/1251 ## [0.16.0] · 2024-03-20 [0.16.0]: /../../tree/juniper-v0.16.0/juniper [Diff](/../../compare/juniper-v0.15.12...juniper-v0.16.0) | [Milestone](/../../milestone/4) ### BC Breaks - [October 2021] GraphQL spec: ([#1000]) - Forbade [`__typename` field on `subscription` operations](https://spec.graphql.org/October2021#note-bc213). ([#1001]) - Supported `isRepeatable` field on directives. ([#1003]) - Supported `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003]) - Supported directives on variables definitions. ([#1005]) - Replaced `Visitor` associated type with `DeserializeOwned` requirement in `ScalarValue` trait. ([#985]) - `#[graphql_object]` and `#[graphql_subscription]` expansions now preserve defined `impl` blocks "as is" and reuse defined methods in opaque way. ([#971]) - Renamed `rename = ""` attribute argument to `rename_all = ""` (following `serde` style). ([#971]) - Upgraded [`bson` crate] integration to [2.0 version](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979]) - Upgraded [`uuid` crate] integration to [1.0 version](https://github.com/uuid-rs/uuid/releases/tag/1.0.0). ([#1057]) - Upgraded [`chrono-tz` crate] integration to [0.8 version](https://github.com/chronotope/chrono-tz/blob/ea628d3131b4a659acb42dbac885cfd08a2e5de9/CHANGELOG.md#080). ([#1119]) - Upgraded [`bigdecimal` crate] integration to 0.4 version. ([#1176]) - Made `FromInputValue` trait methods fallible to allow post-validation. ([#987]) - Redesigned `#[graphql_interface]` macro: ([#1009]) - Removed support for `dyn` attribute argument (interface values as trait objects). - Removed support for `downcast` attribute argument (custom resolution into implementer types). - Removed support for `async` trait methods (not required anymore). - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). ([#113]) - Forbade default implementations of non-ignored trait methods. - Supported coercion of additional `null`able arguments and return sub-typing on implementer. - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) - Supported interfaces implementing other interfaces. ([#1028]) - Split `#[derive(GraphQLScalarValue)]` macro into: - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) - Supported generic `ScalarValue`. - Supported structs with single named field. - Supported overriding resolvers with external functions, methods or modules. - Supported `specified_by_url` attribute argument. ([#1003], [#1000]) - `#[derive(ScalarValue)]` for implementing `ScalarValue` trait: ([#1025]) - Removed `Serialize` implementation (now should be provided explicitly). ([#985]) - Redesigned `#[graphql_scalar]` macro: ([#1014]) - Changed `from_input_value()` return type from `Option` to `Result`. ([#987]) - Mirrored new `#[derive(GraphQLScalar)]` macro. - Supported usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules]. - Renamed `ScalarValue::as_boolean` method to `ScalarValue::as_bool`. ([#1025]) - Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010]) - Disabled `chrono` [Cargo feature] by default. - Removed `scalar-naivetime` [Cargo feature]. - Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#1081], [#528]) - Upgraded [GraphiQL] to 3.1.1 version (requires new [`graphql-transport-ws` GraphQL over WebSocket Protocol] integration on server, see `juniper_warp/examples/subscription.rs`). ([#1188], [#1193], [#1246]) - Abstracted `Spanning::start` and `Spanning::end` fields into separate struct `Span`. ([#1207], [#1208]) - Removed `graphql-parser-integration` and `graphql-parser` [Cargo feature]s by merging them into `schema-language` [Cargo feature]. ([#1237]) - Renamed `RootNode::as_schema_language()` method as `RootNode::as_sdl()`. ([#1237]) - Renamed `RootNode::as_parser_document()` method as `RootNode::as_document()`. ([#1237]) - Reworked look-ahead machinery: ([#1212]) - Turned from eagerly-evaluated into lazy-evaluated: - Made `LookAheadValue::List` to contain new iterable `LookAheadList` type. - Made `LookAheadValue::Object` to contain new iterable `LookAheadObject` type. - Removed `LookAheadMethods` trait and redundant `ConcreteLookAheadSelection` type, making all APIs accessible as inherent methods on `LookAheadSelection` and `LookAheadChildren` decoupled types: - Moved `LookAheadMethods::child_names()` to `LookAheadChildren::names()`. - Moved `LookAheadMethods::has_children()` to `LookAheadChildren::is_empty()`. - Moved `LookAheadMethods::select_child()` to `LookAheadChildren::select()`. - Moved `LookAheadSelection::for_explicit_type()` to `LookAheadSelection::children_for_explicit_type()`. - Made `LookAheadSelection::arguments()` returning iterator over `LookAheadArgument`. - Made `LookAheadSelection::children()` returning `LookAheadChildren`. - Added `Span` to `Arguments` and `LookAheadArguments`. ([#1206], [#1209]) - Disabled `bson`, `url`, `uuid` and `schema-language` [Cargo feature]s by default. ([#1230]) ### Added - Usage of Rust arrays as GraphQL lists. ([#966], [#918]) - `From` implementations for `InputValue` mirroring the ones for `Value` and better support for `Option` handling. ([#996]) - `null` in addition to `None` for creating `Value::Null` in `graphql_value!` macro (following `serde_json::json!` style). ([#996]) - `graphql_input_value!` and `graphql_vars!` macros. ([#996]) - [`time` crate] integration behind `time` [Cargo feature]. ([#1006]) - `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026]) - [`bigdecimal` crate] integration behind `bigdecimal` [Cargo feature]. ([#1060]) - [`rust_decimal` crate] integration behind `rust_decimal` [Cargo feature]. ([#1060]) - `js` [Cargo feature] enabling `js-sys` and `wasm-bindgen` support for `wasm32-unknown-unknown` target. ([#1118], [#1147]) - `LookAheadMethods::applies_for()` method. ([#1138], [#1145]) - `LookAheadMethods::field_original_name()` and `LookAheadMethods::field_alias()` methods. ([#1199]) - [`anyhow` crate] integration behind `anyhow` and `backtrace` [Cargo feature]s. ([#1215], [#988]) - `RootNode::disable_introspection()` applying additional `validation::rules::disable_introspection`, and `RootNode::enable_introspection()` reverting it. ([#1227], [#456]) - `Clone` and `PartialEq` implementations for `GraphQLResponse`. ([#1228], [#103]) ### Changed - Made `GraphQLRequest` fields public. ([#750]) - Relaxed [object safety] requirement for `GraphQLValue` and `GraphQLValueAsync` traits. ([ba1ed85b]) - Updated [GraphQL Playground] to 1.7.28 version. ([#1190]) - Improve validation errors for input values. ([#811], [#693]) ## Fixed - Unsupported spreading GraphQL interface fragments on unions and other interfaces. ([#965], [#798]) - Unsupported expressions in `graphql_value!` macro. ([#996], [#503]) - Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004]) - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) - Incorrect input value coercion with defaults. ([#1080], [#1073]) - Incorrect error when explicit `null` provided for `null`able list input parameter. ([#1086], [#1085]) - Stack overflow on nested GraphQL fragments. ([CVE-2022-31173]) - Unstable definitions order in schema generated by `RootNode::as_sdl()`. ([#1237], [#1134]) - Unstable definitions order in schema generated by `introspect()` or other introspection queries. ([#1239], [#1134]) [#103]: /../../issues/103 [#113]: /../../issues/113 [#456]: /../../issues/456 [#503]: /../../issues/503 [#528]: /../../issues/528 [#693]: /../../issues/693 [#750]: /../../issues/750 [#798]: /../../issues/798 [#811]: /../../pull/811 [#918]: /../../issues/918 [#965]: /../../pull/965 [#966]: /../../pull/966 [#971]: /../../pull/971 [#979]: /../../pull/979 [#985]: /../../pull/985 [#987]: /../../pull/987 [#988]: /../../issues/988 [#996]: /../../pull/996 [#1000]: /../../issues/1000 [#1001]: /../../pull/1001 [#1003]: /../../pull/1003 [#1004]: /../../pull/1004 [#1005]: /../../pull/1005 [#1006]: /../../pull/1006 [#1009]: /../../pull/1009 [#1010]: /../../pull/1010 [#1014]: /../../pull/1014 [#1017]: /../../pull/1017 [#1025]: /../../pull/1025 [#1026]: /../../pull/1026 [#1028]: /../../pull/1028 [#1051]: /../../issues/1051 [#1054]: /../../pull/1054 [#1057]: /../../pull/1057 [#1060]: /../../pull/1060 [#1073]: /../../issues/1073 [#1080]: /../../pull/1080 [#1081]: /../../pull/1081 [#1085]: /../../issues/1085 [#1086]: /../../pull/1086 [#1118]: /../../issues/1118 [#1119]: /../../pull/1119 [#1134]: /../../issues/1134 [#1138]: /../../issues/1138 [#1145]: /../../pull/1145 [#1147]: /../../pull/1147 [#1176]: /../../pull/1176 [#1188]: /../../pull/1188 [#1190]: /../../pull/1190 [#1193]: /../../pull/1193 [#1199]: /../../pull/1199 [#1206]: /../../pull/1206 [#1207]: /../../pull/1207 [#1208]: /../../pull/1208 [#1209]: /../../pull/1209 [#1212]: /../../pull/1212 [#1215]: /../../pull/1215 [#1227]: /../../pull/1227 [#1228]: /../../pull/1228 [#1230]: /../../pull/1230 [#1237]: /../../pull/1237 [#1239]: /../../pull/1239 [#1246]: /../../pull/1246 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 [CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j ## Previous releases See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md). [`anyhow` crate]: https://docs.rs/anyhow [`bigdecimal` crate]: https://docs.rs/bigdecimal [`bson` crate]: https://docs.rs/bson [`chrono` crate]: https://docs.rs/chrono [`chrono-tz` crate]: https://docs.rs/chrono-tz [`time` crate]: https://docs.rs/time [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html [`graphql-transport-ws` GraphQL over WebSocket Protocol]: https://github.com/enisdenjo/graphql-ws/v5.14.0/PROTOCOL.md [GraphiQL]: https://github.com/graphql/graphiql [GraphQL Playground]: https://github.com/prisma/graphql-playground [graphql-scalars.dev]: https://graphql-scalars.dev [October 2021]: https://spec.graphql.org/October2021 [object safety]: https://doc.rust-lang.org/reference/items/traits.html#object-safety [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules [Semantic Versioning 2.0.0]: https://semver.org juniper-0.16.2/Cargo.lock0000644000001237570000000000100105700ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.16", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" dependencies = [ "backtrace", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-trait" version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "auto_enums" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c170965892137a3a9aeb000b4524aa3cc022a310e709d848b6e1cdce4ab4781" dependencies = [ "derive_utils", "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bencher" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" [[package]] name = "bigdecimal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "bson" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8113ff51309e2779e8785a246c10fb783e8c2452f134d6257fd71cc03ccd6c" dependencies = [ "ahash", "base64", "bitvec", "chrono", "getrandom 0.2.16", "getrandom 0.3.2", "hex", "indexmap", "js-sys", "once_cell", "rand 0.9.1", "serde", "serde_bytes", "serde_json", "time", "uuid", ] [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "chrono-tz" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "derive_utils" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccfae181bab5ab6c5478b2ccb69e4c68a02f8c3ec72f6616bfec9dbc599d2ee0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "graphql-parser" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a818c0d883d7c0801df27be910917750932be279c7bc82dc541b8769425f409" dependencies = [ "combine", "thiserror", ] [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "juniper" version = "0.16.2" dependencies = [ "anyhow", "async-trait", "auto_enums", "bencher", "bigdecimal", "bson", "chrono", "chrono-tz", "fnv", "futures", "graphql-parser", "indexmap", "juniper_codegen", "num-bigint", "pretty_assertions", "regex", "rust_decimal", "ryu", "serde", "serde_json", "serial_test", "smartstring", "static_assertions", "tap", "time", "tokio", "url", "uuid", "void", ] [[package]] name = "juniper_codegen" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760dbe46660494d469023d661e8d268f413b2cb68c999975dcc237407096a693" dependencies = [ "proc-macro2", "quote", "syn", "url", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy 0.8.25", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.2", ] [[package]] name = "redox_syscall" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rust_decimal" version = "1.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" dependencies = [ "arrayvec", "num-traits", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scc" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" dependencies = [ "sdd", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "indexmap", "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serial_test" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ "futures", "log", "once_cell", "parking_lot", "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smartstring" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ "autocfg", "static_assertions", "version_check", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "js-sys", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom 0.3.2", "js-sys", "serde", "wasm-bindgen", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive 0.8.25", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerocopy-derive" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] juniper-0.16.2/Cargo.toml0000644000000102150000000000100105730ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.73" name = "juniper" version = "0.16.2" authors = [ "Magnus Hallin ", "Christoph Herzog ", "Christian Legnitto ", "Ilya Solovyiov ", "Kai Ren ", ] build = false include = [ "/src/", "/CHANGELOG.md", "/LICENSE", "/README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "GraphQL server library." homepage = "https://graphql-rust.github.io/juniper" documentation = "https://docs.rs/juniper" readme = "README.md" keywords = [ "apollo", "graphql", "server", "web", ] categories = [ "asynchronous", "web-programming", "web-programming::http-server", ] license = "BSD-2-Clause" repository = "https://github.com/graphql-rust/juniper" resolver = "1" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] anyhow = ["dep:anyhow"] backtrace = ["anyhow?/backtrace"] bigdecimal = [ "dep:bigdecimal", "dep:num-bigint", "dep:ryu", ] bson = [ "dep:bson", "dep:tap", ] chrono = ["dep:chrono"] chrono-clock = [ "chrono", "chrono/clock", ] chrono-tz = [ "dep:chrono-tz", "dep:regex", ] expose-test-schema = [ "dep:anyhow", "dep:serde_json", ] js = [ "chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js", ] rust_decimal = ["dep:rust_decimal"] schema-language = [ "dep:graphql-parser", "dep:void", ] time = ["dep:time"] url = ["dep:url"] uuid = ["dep:uuid"] [lib] name = "juniper" path = "src/lib.rs" [dependencies.anyhow] version = "1.0.47" optional = true [dependencies.async-trait] version = "0.1.39" [dependencies.auto_enums] version = "0.8" [dependencies.bigdecimal] version = "0.4" optional = true [dependencies.bson] version = "2.4" features = ["chrono-0_4"] optional = true [dependencies.chrono] version = "0.4.30" features = ["alloc"] optional = true default-features = false [dependencies.chrono-tz] version = "0.8" optional = true default-features = false [dependencies.fnv] version = "1.0.5" [dependencies.futures] version = "0.3.22" features = ["alloc"] default-features = false [dependencies.graphql-parser] version = "0.4" optional = true [dependencies.indexmap] version = "2.0" features = ["serde"] [dependencies.juniper_codegen] version = "0.16.0" [dependencies.num-bigint] version = "0.4.2" optional = true [dependencies.regex] version = "1.6" features = ["std"] optional = true default-features = false [dependencies.rust_decimal] version = "1.20" optional = true default-features = false [dependencies.ryu] version = "1.0" optional = true [dependencies.serde] version = "1.0.122" features = ["derive"] [dependencies.serde_json] version = "1.0.18" features = ["std"] optional = true default-features = false [dependencies.smartstring] version = "1.0" [dependencies.static_assertions] version = "1.1" [dependencies.tap] version = "1.0.1" optional = true [dependencies.time] version = "0.3" features = [ "formatting", "macros", "parsing", ] optional = true [dependencies.url] version = "2.0" optional = true [dependencies.uuid] version = "1.3" optional = true default-features = false [dependencies.void] version = "1.0.2" optional = true [dev-dependencies.bencher] version = "0.1.2" [dev-dependencies.chrono] version = "0.4.30" features = ["alloc"] default-features = false [dev-dependencies.pretty_assertions] version = "1.0.0" [dev-dependencies.serde_json] version = "1.0.18" [dev-dependencies.serial_test] version = "3.0" [dev-dependencies.tokio] version = "1.0" features = [ "macros", "time", "rt-multi-thread", ] juniper-0.16.2/Cargo.toml.orig000064400000000000000000000064741046102023000142700ustar 00000000000000[package] name = "juniper" version = "0.16.2" edition = "2021" rust-version = "1.73" description = "GraphQL server library." license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", "Christian Legnitto ", "Ilya Solovyiov ", "Kai Ren ", ] documentation = "https://docs.rs/juniper" homepage = "https://graphql-rust.github.io/juniper" repository = "https://github.com/graphql-rust/juniper" readme = "README.md" categories = ["asynchronous", "web-programming", "web-programming::http-server"] keywords = ["apollo", "graphql", "server", "web"] include = ["/src/", "/CHANGELOG.md", "/LICENSE", "/README.md"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] anyhow = ["dep:anyhow"] backtrace = ["anyhow?/backtrace"] bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "dep:ryu"] bson = ["dep:bson", "dep:tap"] chrono = ["dep:chrono"] chrono-clock = ["chrono", "chrono/clock"] chrono-tz = ["dep:chrono-tz", "dep:regex"] expose-test-schema = ["dep:anyhow", "dep:serde_json"] js = ["chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js"] rust_decimal = ["dep:rust_decimal"] schema-language = ["dep:graphql-parser", "dep:void"] time = ["dep:time"] url = ["dep:url"] uuid = ["dep:uuid"] [dependencies] anyhow = { version = "1.0.47", optional = true } async-trait = "0.1.39" auto_enums = "0.8" bigdecimal = { version = "0.4", optional = true } bson = { version = "2.4", features = ["chrono-0_4"], optional = true } chrono = { version = "0.4.30", features = ["alloc"], default-features = false, optional = true } chrono-tz = { version = "0.8", default-features = false, optional = true } fnv = "1.0.5" futures = { version = "0.3.22", features = ["alloc"], default-features = false } graphql-parser = { version = "0.4", optional = true } indexmap = { version = "2.0", features = ["serde"] } juniper_codegen = { version = "0.16.0", path = "../juniper_codegen" } rust_decimal = { version = "1.20", default-features = false, optional = true } ryu = { version = "1.0", optional = true } serde = { version = "1.0.122", features = ["derive"] } serde_json = { version = "1.0.18", features = ["std"], default-features = false, optional = true } smartstring = "1.0" static_assertions = "1.1" time = { version = "0.3", features = ["formatting", "macros", "parsing"], optional = true } url = { version = "2.0", optional = true } uuid = { version = "1.3", default-features = false, optional = true } # Fixes for `minimal-versions` check. # TODO: Try remove on upgrade of `bigdecimal` crate. num-bigint = { version = "0.4.2", optional = true } # TODO: Try remove on upgrade of `chrono-tz` crate. regex = { version = "1.6", features = ["std"], default-features = false, optional = true } # TODO: Try remove on upgrade of `bson` crate. tap = { version = "1.0.1", optional = true } # TODO: Remove on upgrade to 0.4.1 version of `graphql-parser`. void = { version = "1.0.2", optional = true } [dev-dependencies] bencher = "0.1.2" chrono = { version = "0.4.30", features = ["alloc"], default-features = false } pretty_assertions = "1.0.0" serde_json = "1.0.18" serial_test = "3.0" tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] } [[bench]] name = "bench" harness = false path = "benches/bench.rs" juniper-0.16.2/LICENSE000064400000000000000000000030741046102023000123770ustar 00000000000000BSD 2-Clause License Copyright (c) 2016-2024 Magnus Hallin , Christoph Herzog , Christian Legnitto , Ilya Solovyiov , Kai Ren All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. juniper-0.16.2/README.md000064400000000000000000000103061046102023000126450ustar 00000000000000Juniper (GraphQL server library for Rust) ========================================= [![Crates.io](https://img.shields.io/crates/v/juniper.svg?maxAge=2592000)](https://crates.io/crates/juniper) [![Documentation](https://docs.rs/juniper/badge.svg)](https://docs.rs/juniper) [![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) [![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html) - [Juniper Book] ([current][Juniper Book] | [edge][Juniper Book edge]) - [Changelog](https://github.com/graphql-rust/juniper/blob/juniper-v0.16.2/juniper/CHANGELOG.md) [GraphQL] is a data query language developed by [Facebook] and intended to serve mobile and web application frontends. *[Juniper]* makes it possible to write [GraphQL] servers in [Rust] that are type-safe and blazingly fast. We also try to make declaring and resolving [GraphQL] schemas as convenient as [Rust] will allow. [Juniper] doesn't include a web server - instead it provides building blocks to make integration with existing servers straightforward, including embedded [GraphiQL] and/or [GraphQL Playground] for easy debugging. ## Getting Started The best place to get started is [Juniper Book], which contains guides with plenty of examples, covering all features of [Juniper]. To get started quickly and get a feel for Juniper, check out the ["Quickstart" section][1]. For specific information about macros, types and the [Juniper] API, the [API docs][Juniper] is the best place to look. ## Features [Juniper] supports the full [GraphQL] query language according to [October 2021 GraphQL specification](https://spec.graphql.org/October2021), including interfaces, unions, schema introspection, and validations. It does not, however, support the schema language. As an exception to other [GraphQL] libraries for other languages, [Juniper] builds non-`null` types by default. A field of type `Vec` will be converted into `[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be `Option>>`. ## Integrations ### Types [Juniper] provides out-of-the-box integration for some very common [Rust] crates to make building schemas a breeze. The types from these crates will be usable in your schemas automatically after enabling the correspondent self-titled [Cargo feature]: - [`bigdecimal`] - [`bson`] - [`chrono`], [`chrono-tz`] - [`rust_decimal`] - [`time`] - [`url`] - [`uuid`] ### Web server frameworks - [`actix-web`] ([`juniper_actix`] crate) - [`axum`] ([`juniper_axum`] crate) - [`hyper`] ([`juniper_hyper`] crate) - [`rocket`] ([`juniper_rocket`] crate) - [`warp`] ([`juniper_warp`] crate) ## API stability [Juniper] has not reached 1.0 yet, thus some API instability should be expected. ## License This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/juniper-v0.16.2/juniper/LICENSE). [`actix-web`]: https://docs.rs/actix-web [`axum`]: https://docs.rs/axum [`bigdecimal`]: https://docs.rs/bigdecimal [`bson`]: https://docs.rs/bson [`chrono`]: https://docs.rs/chrono [`chrono-tz`]: https://docs.rs/chrono-tz [`juniper_actix`]: https://docs.rs/juniper_actix [`juniper_axum`]: https://docs.rs/juniper_axum [`juniper_hyper`]: https://docs.rs/juniper_hyper [`juniper_rocket`]: https://docs.rs/juniper_rocket [`juniper_warp`]: https://docs.rs/juniper_warp [`hyper`]: https://docs.rs/hyper [`rocket`]: https://docs.rs/rocket [`rust_decimal`]: https://docs.rs/rust_decimal [`time`]: https://docs.rs/time [`url`]: https://docs.rs/url [`uuid`]: https://docs.rs/uuid [`warp`]: https://docs.rs/warp [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html [Facebook]: https://facebook.com [GraphiQL]: https://github.com/graphql/graphiql [GraphQL]: http://graphql.org [GraphQL Playground]: https://github.com/graphql/graphql-playground [Juniper]: https://docs.rs/juniper [Juniper Book]: https://graphql-rust.github.io/juniper [Juniper Book edge]: https://graphql-rust.github.io/juniper/master [Rust]: https://www.rust-lang.org [1]: https://graphql-rust.github.io/juniper/quickstart.html juniper-0.16.2/src/ast.rs000064400000000000000000000446011046102023000133170ustar 00000000000000use std::{borrow::Cow, fmt, hash::Hash, slice, vec}; use indexmap::IndexMap; use crate::{ executor::Variables, parser::Spanning, value::{DefaultScalarValue, ScalarValue}, }; /// A type literal in the syntax tree /// /// This enum carries no semantic information and might refer to types that do /// not exist. #[derive(Clone, Eq, PartialEq, Debug)] pub enum Type<'a> { /// A nullable named type, e.g. `String` Named(Cow<'a, str>), /// A nullable list type, e.g. `[String]` /// /// The list itself is what's nullable, the containing type might be non-null. List(Box>, Option), /// A non-null named type, e.g. `String!` NonNullNamed(Cow<'a, str>), /// A non-null list type, e.g. `[String]!`. /// /// The list itself is what's non-null, the containing type might be null. NonNullList(Box>, Option), } /// A JSON-like value that can be passed into the query execution, either /// out-of-band, or in-band as default variable values. These are _not_ constant /// and might contain variables. /// /// Lists and objects variants are _spanned_, i.e. they contain a reference to /// their position in the source file, if available. #[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] pub enum InputValue { Null, Scalar(S), Enum(String), Variable(String), List(Vec>>), Object(Vec<(Spanning, Spanning>)>), } #[derive(Clone, PartialEq, Debug)] pub struct VariableDefinition<'a, S> { pub var_type: Spanning>, pub default_value: Option>>, pub directives: Option>>>, } #[derive(Clone, PartialEq, Debug)] pub struct Arguments<'a, S> { pub items: Vec<(Spanning<&'a str>, Spanning>)>, } #[derive(Clone, PartialEq, Debug)] pub struct VariableDefinitions<'a, S> { pub items: Vec<(Spanning<&'a str>, VariableDefinition<'a, S>)>, } #[derive(Clone, PartialEq, Debug)] pub struct Field<'a, S> { pub alias: Option>, pub name: Spanning<&'a str>, pub arguments: Option>>, pub directives: Option>>>, pub selection_set: Option>>, } #[derive(Clone, PartialEq, Debug)] pub struct FragmentSpread<'a, S> { pub name: Spanning<&'a str>, pub directives: Option>>>, } #[derive(Clone, PartialEq, Debug)] pub struct InlineFragment<'a, S> { pub type_condition: Option>, pub directives: Option>>>, pub selection_set: Vec>, } /// Entry in a GraphQL selection set /// /// This enum represents one of the three variants of a selection that exists /// in GraphQL: a field, a fragment spread, or an inline fragment. Each of the /// variants references their location in the query source. /// /// ```text /// { /// field(withArg: 123) { subField } /// ...fragmentSpread /// ...on User { /// inlineFragmentField /// } /// } /// ``` #[derive(Clone, PartialEq, Debug)] #[allow(missing_docs)] pub enum Selection<'a, S = DefaultScalarValue> { Field(Spanning>), FragmentSpread(Spanning>), InlineFragment(Spanning>), } #[derive(Clone, PartialEq, Debug)] pub struct Directive<'a, S> { pub name: Spanning<&'a str>, pub arguments: Option>>, } #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum OperationType { Query, Mutation, Subscription, } #[allow(missing_docs)] #[derive(Clone, PartialEq, Debug)] pub struct Operation<'a, S> { pub operation_type: OperationType, pub name: Option>, pub variable_definitions: Option>>, pub directives: Option>>>, pub selection_set: Vec>, } #[derive(Clone, PartialEq, Debug)] pub struct Fragment<'a, S> { pub name: Spanning<&'a str>, pub type_condition: Spanning<&'a str>, pub directives: Option>>>, pub selection_set: Vec>, } #[doc(hidden)] #[derive(Clone, PartialEq, Debug)] pub enum Definition<'a, S> { Operation(Spanning>), Fragment(Spanning>), } #[doc(hidden)] pub type Document<'a, S> = [Definition<'a, S>]; #[doc(hidden)] pub type OwnedDocument<'a, S> = Vec>; /// Parsing of an unstructured input value into a Rust data type. /// /// The conversion _can_ fail, and must in that case return [`Err`]. Thus not /// restricted in the definition of this trait, the returned [`Err`] should be /// convertible with [`IntoFieldError`] to fit well into the library machinery. /// /// Implemented automatically by the convenience proc macro [`graphql_scalar!`] /// or by deriving `GraphQLEnum`. /// /// Must be implemented manually when manually exposing new enums or scalars. /// /// [`graphql_scalar!`]: macro@crate::graphql_scalar /// [`IntoFieldError`]: crate::IntoFieldError pub trait FromInputValue: Sized { /// Type of this conversion error. /// /// Thus not restricted, it should be convertible with [`IntoFieldError`] to /// fit well into the library machinery. /// /// [`IntoFieldError`]: crate::IntoFieldError type Error; /// Performs the conversion. fn from_input_value(v: &InputValue) -> Result; /// Performs the conversion from an absent value (e.g. to distinguish /// between implicit and explicit `null`). /// /// The default implementation just calls [`from_input_value()`] as if an /// explicit `null` was provided. /// /// [`from_input_value()`]: FromInputValue::from_input_value fn from_implicit_null() -> Result { Self::from_input_value(&InputValue::::Null) } } /// Losslessly clones a Rust data type into an InputValue. pub trait ToInputValue: Sized { /// Performs the conversion. fn to_input_value(&self) -> InputValue; } impl<'a> Type<'a> { /// Get the name of a named type. /// /// Only applies to named types; lists will return `None`. pub fn name(&self) -> Option<&str> { match *self { Type::Named(ref n) | Type::NonNullNamed(ref n) => Some(n), _ => None, } } /// Get the innermost name by unpacking lists /// /// All type literals contain exactly one named type. pub fn innermost_name(&self) -> &str { match *self { Type::Named(ref n) | Type::NonNullNamed(ref n) => n, Type::List(ref l, _) | Type::NonNullList(ref l, _) => l.innermost_name(), } } /// Determines if a type only can represent non-null values. pub fn is_non_null(&self) -> bool { matches!(*self, Type::NonNullNamed(_) | Type::NonNullList(..)) } } impl<'a> fmt::Display for Type<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Named(n) => write!(f, "{n}"), Self::NonNullNamed(n) => write!(f, "{n}!"), Self::List(t, _) => write!(f, "[{t}]"), Self::NonNullList(t, _) => write!(f, "[{t}]!"), } } } impl InputValue { /// Construct a `null` value. pub fn null() -> Self { Self::Null } /// Construct a scalar value pub fn scalar(v: T) -> Self where S: From, { Self::Scalar(v.into()) } /// Construct an enum value. pub fn enum_value>(s: T) -> Self { Self::Enum(s.as_ref().into()) } /// Construct a variable value. pub fn variable>(v: T) -> Self { Self::Variable(v.as_ref().into()) } /// Construct a [`Spanning::unlocated`] list. /// /// Convenience function to make each [`InputValue`] in the input vector /// not contain any location information. Can be used from [`ToInputValue`] /// implementations, where no source code position information is available. pub fn list(l: Vec) -> Self { Self::List(l.into_iter().map(Spanning::unlocated).collect()) } /// Construct a located list. pub fn parsed_list(l: Vec>) -> Self { Self::List(l) } /// Construct aa [`Spanning::unlocated`] object. /// /// Similarly to [`InputValue::list`] it makes each key and value in the /// given hash map not contain any location information. pub fn object(o: IndexMap) -> Self where K: AsRef + Eq + Hash, { Self::Object( o.into_iter() .map(|(k, v)| { ( Spanning::unlocated(k.as_ref().into()), Spanning::unlocated(v), ) }) .collect(), ) } /// Construct a located object. pub fn parsed_object(o: Vec<(Spanning, Spanning)>) -> Self { Self::Object(o) } /// Resolves all variables of this [`InputValue`] to their actual `values`. /// /// If a variable is not present in the `values`: /// - Returns [`None`] in case this is an [`InputValue::Variable`]. /// - Skips field in case of an [`InputValue::Object`] field. /// - Replaces with an [`InputValue::Null`] in case of an /// [`InputValue::List`] element. /// /// This is done, because for an [`InputValue::Variable`] (or an /// [`InputValue::Object`] field) a default value can be used later, if it's /// provided. While on contrary, a single [`InputValue::List`] element /// cannot have a default value. #[must_use] pub fn into_const(self, values: &Variables) -> Option where S: Clone, { match self { Self::Variable(v) => values.get(&v).cloned(), Self::List(l) => Some(Self::List( l.into_iter() .map(|s| s.map(|v| v.into_const(values).unwrap_or_else(Self::null))) .collect(), )), Self::Object(o) => Some(Self::Object( o.into_iter() .filter_map(|(sk, sv)| sv.and_then(|v| v.into_const(values)).map(|sv| (sk, sv))) .collect(), )), v => Some(v), } } /// Shorthand form of invoking [`FromInputValue::from_input_value()`]. pub fn convert>(&self) -> Result { T::from_input_value(self) } /// Does the value represent a `null`? pub fn is_null(&self) -> bool { matches!(self, Self::Null) } /// Does the value represent a variable? pub fn is_variable(&self) -> bool { matches!(self, Self::Variable(_)) } /// View the underlying enum value, if present. pub fn as_enum_value(&self) -> Option<&str> { match self { Self::Enum(e) => Some(e.as_str()), _ => None, } } /// View the underlying int value, if present. pub fn as_int_value(&self) -> Option where S: ScalarValue, { self.as_scalar_value().and_then(|s| s.as_int()) } /// View the underlying float value, if present. pub fn as_float_value(&self) -> Option where S: ScalarValue, { self.as_scalar_value().and_then(|s| s.as_float()) } /// View the underlying string value, if present. pub fn as_string_value(&self) -> Option<&str> where S: ScalarValue, { self.as_scalar_value().and_then(|s| s.as_str()) } /// View the underlying scalar value, if present. pub fn as_scalar(&self) -> Option<&S> { match self { Self::Scalar(s) => Some(s), _ => None, } } /// View the underlying scalar value, if present. pub fn as_scalar_value<'a, T>(&'a self) -> Option<&'a T> where T: 'a, Option<&'a T>: From<&'a S>, { self.as_scalar().and_then(Into::into) } /// Converts this [`InputValue`] to a [`Spanning::unlocated`] object value. /// /// This constructs a new [`IndexMap`] containing references to the keys /// and values of `self`. pub fn to_object_value(&self) -> Option> { match self { Self::Object(o) => Some( o.iter() .map(|(sk, sv)| (sk.item.as_str(), &sv.item)) .collect(), ), _ => None, } } /// Converts this [`InputValue`] to a [`Spanning::unlocated`] list value. /// /// This constructs a new [`Vec`] containing references to the values of /// `self`. pub fn to_list_value(&self) -> Option> { match self { Self::List(l) => Some(l.iter().map(|s| &s.item).collect()), _ => None, } } /// Recursively finds all variables pub fn referenced_variables(&self) -> Vec<&str> { match self { Self::Variable(name) => vec![name.as_str()], Self::List(l) => l .iter() .flat_map(|v| v.item.referenced_variables()) .collect(), Self::Object(o) => o .iter() .flat_map(|(_, v)| v.item.referenced_variables()) .collect(), _ => vec![], } } /// Compares equality with another [`InputValue``] ignoring any source /// position information. pub fn unlocated_eq(&self, other: &Self) -> bool where S: PartialEq, { match (self, other) { (Self::Null, Self::Null) => true, (Self::Scalar(s1), Self::Scalar(s2)) => s1 == s2, (Self::Enum(s1), Self::Enum(s2)) | (Self::Variable(s1), Self::Variable(s2)) => s1 == s2, (Self::List(l1), Self::List(l2)) => l1 .iter() .zip(l2.iter()) .all(|(v1, v2)| v1.item.unlocated_eq(&v2.item)), (Self::Object(o1), Self::Object(o2)) => { o1.len() == o2.len() && o1.iter().all(|(sk1, sv1)| { o2.iter().any(|(sk2, sv2)| { sk1.item == sk2.item && sv1.item.unlocated_eq(&sv2.item) }) }) } _ => false, } } } impl fmt::Display for InputValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Null => write!(f, "null"), Self::Scalar(s) => { if let Some(s) = s.as_str() { write!(f, "\"{s}\"") } else { write!(f, "{s}") } } Self::Enum(v) => write!(f, "{v}"), Self::Variable(v) => write!(f, "${v}"), Self::List(v) => { write!(f, "[")?; for (i, spanning) in v.iter().enumerate() { spanning.item.fmt(f)?; if i < v.len() - 1 { write!(f, ", ")?; } } write!(f, "]") } Self::Object(o) => { write!(f, "{{")?; for (i, (k, v)) in o.iter().enumerate() { write!(f, "{}: ", k.item)?; v.item.fmt(f)?; if i < o.len() - 1 { write!(f, ", ")?; } } write!(f, "}}") } } } } impl From> for InputValue where Self: From, { fn from(v: Option) -> Self { match v { Some(v) => v.into(), None => Self::Null, } } } impl<'a, S: From> From<&'a str> for InputValue { fn from(s: &'a str) -> Self { Self::scalar(s.to_owned()) } } impl<'a, S: From> From> for InputValue { fn from(s: Cow<'a, str>) -> Self { Self::scalar(s.into_owned()) } } impl> From for InputValue { fn from(s: String) -> Self { Self::scalar(s) } } impl> From for InputValue { fn from(i: i32) -> Self { Self::scalar(i) } } impl> From for InputValue { fn from(f: f64) -> Self { Self::scalar(f) } } impl> From for InputValue { fn from(b: bool) -> Self { Self::scalar(b) } } impl<'a, S> Arguments<'a, S> { pub fn into_iter(self) -> vec::IntoIter<(Spanning<&'a str>, Spanning>)> { self.items.into_iter() } pub fn iter(&self) -> slice::Iter<(Spanning<&'a str>, Spanning>)> { self.items.iter() } pub fn iter_mut(&mut self) -> slice::IterMut<(Spanning<&'a str>, Spanning>)> { self.items.iter_mut() } pub fn len(&self) -> usize { self.items.len() } pub fn get(&self, key: &str) -> Option<&Spanning>> { self.items .iter() .filter(|&(k, _)| k.item == key) .map(|(_, v)| v) .next() } } impl<'a, S> VariableDefinitions<'a, S> { pub fn iter(&self) -> slice::Iter<(Spanning<&'a str>, VariableDefinition)> { self.items.iter() } } #[cfg(test)] mod tests { use crate::graphql_input_value; use super::InputValue; #[test] fn test_input_value_fmt() { let value: InputValue = graphql_input_value!(null); assert_eq!(value.to_string(), "null"); let value: InputValue = graphql_input_value!(123); assert_eq!(value.to_string(), "123"); let value: InputValue = graphql_input_value!(12.3); assert_eq!(value.to_string(), "12.3"); let value: InputValue = graphql_input_value!("FOO"); assert_eq!(value.to_string(), "\"FOO\""); let value: InputValue = graphql_input_value!(true); assert_eq!(value.to_string(), "true"); let value: InputValue = graphql_input_value!(BAR); assert_eq!(value.to_string(), "BAR"); let value: InputValue = graphql_input_value!(@baz); assert_eq!(value.to_string(), "$baz"); let value: InputValue = graphql_input_value!([1, 2]); assert_eq!(value.to_string(), "[1, 2]"); let value: InputValue = graphql_input_value!({"foo": 1,"bar": 2}); assert_eq!(value.to_string(), "{foo: 1, bar: 2}"); } } juniper-0.16.2/src/executor/look_ahead.rs000064400000000000000000001743171046102023000164640ustar 00000000000000use std::{collections::HashMap, vec}; use crate::{ ast::{Directive, Field, Fragment, InputValue, Selection}, parser::{Span, Spanning}, value::ScalarValue, }; use super::Variables; /// Indication whether a field is available in all types of an interface or only in a certain /// subtype. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Applies<'a> { /// Field is always available, independently from the type. All, /// Field is only available for the type with the specified typename. OnlyType(&'a str), } /// Shortcut for a [`Spanning`] containing a borrowed [`Span`]. type BorrowedSpanning<'a, T> = Spanning; /// JSON-like value performing [look-ahead][0] operations on an executed GraphQL query. /// /// In contrast to an [`InputValue`], these values do only contain constants, meaning that GraphQL /// variables get automatically resolved. /// /// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) #[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] #[must_use] pub enum LookAheadValue<'a, S: ScalarValue + 'a> { Null, Scalar(&'a S), Enum(&'a str), List(LookAheadList<'a, S>), Object(LookAheadObject<'a, S>), } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S: ScalarValue + 'a> Copy for LookAheadValue<'a, S> where Self: Clone {} impl<'a, S: ScalarValue + 'a> LookAheadValue<'a, S> { fn from_input_value( input_value: BorrowedSpanning<'a, &'a InputValue>, vars: Option<&'a Variables>, ) -> BorrowedSpanning<'a, Self> { let Spanning { item: input_value, span: input_span, } = input_value; Spanning { item: match input_value { InputValue::Null => Self::Null, InputValue::Scalar(s) => Self::Scalar(s), InputValue::Enum(e) => Self::Enum(e), InputValue::Variable(name) => vars .and_then(|vars| vars.get(name)) .map(|item| { Self::from_input_value( BorrowedSpanning { item, span: input_span, }, vars, ) .item }) .unwrap_or(Self::Null), InputValue::List(input_list) => Self::List(LookAheadList { input_list, vars }), InputValue::Object(input_object) => Self::Object(LookAheadObject { input_object: input_object.as_slice(), vars, }), }, span: input_span, } } } /// [Lazy][2]-evaluated [list] used in [look-ahead][0] operations on an executed GraphQL query. /// /// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) /// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation /// [list]: https://spec.graphql.org/October2021#sec-List #[derive(Debug)] #[must_use] pub struct LookAheadList<'a, S> { input_list: &'a [Spanning>], vars: Option<&'a Variables>, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for LookAheadList<'a, S> { fn clone(&self) -> Self { *self } } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S> Copy for LookAheadList<'a, S> {} // Implemented manually to omit redundant `S: Default` trait bound, imposed by `#[derive(Default)]`. impl<'a, S> Default for LookAheadList<'a, S> { fn default() -> Self { Self { input_list: &[], vars: None, } } } // Implemented manually to omit redundant `S: PartialEq` trait bound, imposed by // `#[derive(PartialEq)]`. impl<'a, S: ScalarValue> PartialEq for LookAheadList<'a, S> { fn eq(&self, other: &Self) -> bool { self.iter().eq(other.iter()) } } impl<'a, S: ScalarValue> LookAheadList<'a, S> { /// Returns an [`Iterator`] over the items of this [list]. /// /// [list]: https://spec.graphql.org/October2021#sec-List pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { self.into_iter() } } impl<'a, S: ScalarValue> IntoIterator for LookAheadList<'a, S> { type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; type IntoIter = look_ahead_list::Iter<'a, S>; fn into_iter(self) -> Self::IntoIter { (&self).into_iter() } } impl<'a, S: ScalarValue> IntoIterator for &LookAheadList<'a, S> { type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; type IntoIter = look_ahead_list::Iter<'a, S>; fn into_iter(self) -> Self::IntoIter { look_ahead_list::Iter { slice_iter: self.input_list.iter(), vars: self.vars, } } } pub mod look_ahead_list { //! [`LookAheadList`] helper definitions. use std::slice; #[cfg(doc)] use super::LookAheadList; use super::{BorrowedSpanning, InputValue, LookAheadValue, ScalarValue, Spanning, Variables}; /// [`Iterator`] over [`LookAheadList`] items ([`LookAheadValue`]s) by value. /// /// GraphQL variables are resolved lazily as this [`Iterator`] advances. #[must_use] pub struct Iter<'a, S> { pub(super) slice_iter: slice::Iter<'a, Spanning>>, pub(super) vars: Option<&'a Variables>, } impl<'a, S: ScalarValue> Iterator for Iter<'a, S> { type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; fn next(&mut self) -> Option { let vars = self.vars; self.slice_iter .next() .map(move |val| LookAheadValue::from_input_value(val.as_ref(), vars)) } } impl<'a, S: ScalarValue> DoubleEndedIterator for Iter<'a, S> { fn next_back(&mut self) -> Option { let vars = self.vars; self.slice_iter .next_back() .map(move |val| LookAheadValue::from_input_value(val.as_ref(), vars)) } } } /// [Lazy][2]-evaluated [input object] used in [look-ahead][0] operations on an executed GraphQL /// query. /// /// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) /// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation /// [input object]: https://spec.graphql.org/October2021#sec-Input-Objects #[derive(Debug)] #[must_use] pub struct LookAheadObject<'a, S> { input_object: &'a [(Spanning, Spanning>)], vars: Option<&'a Variables>, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for LookAheadObject<'a, S> { fn clone(&self) -> Self { *self } } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S> Copy for LookAheadObject<'a, S> {} impl<'a, S> Default for LookAheadObject<'a, S> { fn default() -> Self { Self { input_object: &[], vars: None, } } } impl<'a, S: ScalarValue> PartialEq for LookAheadObject<'a, S> { fn eq(&self, other: &Self) -> bool { self.iter().eq(other.iter()) } } impl<'a, S: ScalarValue> LookAheadObject<'a, S> { /// Returns an [`Iterator`] over this [input object]'s fields. /// /// [input object]: https://spec.graphql.org/October2021#sec-Input-Objects pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { self.into_iter() } } impl<'a, S: ScalarValue> IntoIterator for LookAheadObject<'a, S> { type Item = ( BorrowedSpanning<'a, &'a str>, BorrowedSpanning<'a, LookAheadValue<'a, S>>, ); type IntoIter = look_ahead_object::Iter<'a, S>; fn into_iter(self) -> Self::IntoIter { (&self).into_iter() } } impl<'a, S: ScalarValue> IntoIterator for &LookAheadObject<'a, S> { type Item = ( BorrowedSpanning<'a, &'a str>, BorrowedSpanning<'a, LookAheadValue<'a, S>>, ); type IntoIter = look_ahead_object::Iter<'a, S>; fn into_iter(self) -> Self::IntoIter { look_ahead_object::Iter { slice_iter: self.input_object.iter(), vars: self.vars, } } } pub mod look_ahead_object { //! [`LookAheadObject`] helper definitions. use std::slice; #[cfg(doc)] use super::LookAheadList; use super::{BorrowedSpanning, InputValue, LookAheadValue, ScalarValue, Spanning, Variables}; /// [`Iterator`] over [`LookAheadObject`] fields (named [`LookAheadValue`]s) by value. /// /// GraphQL variables are resolved lazily as this [`Iterator`] advances. #[must_use] pub struct Iter<'a, S> { pub(super) slice_iter: slice::Iter<'a, (Spanning, Spanning>)>, pub(super) vars: Option<&'a Variables>, } impl<'a, S: ScalarValue> Iterator for Iter<'a, S> { type Item = ( BorrowedSpanning<'a, &'a str>, BorrowedSpanning<'a, LookAheadValue<'a, S>>, ); fn next(&mut self) -> Option { let vars = self.vars; self.slice_iter.next().map(move |(key, val)| { ( Spanning { span: &key.span, item: key.item.as_str(), }, LookAheadValue::from_input_value(val.as_ref(), vars), ) }) } } impl<'a, S: ScalarValue> DoubleEndedIterator for Iter<'a, S> { fn next_back(&mut self) -> Option { let vars = self.vars; self.slice_iter.next_back().map(move |(key, val)| { ( Spanning { span: &key.span, item: key.item.as_str(), }, LookAheadValue::from_input_value(val.as_ref(), vars), ) }) } } } /// [Lazy][2]-evaluated [argument] used in [look-ahead][0] operations on an executed GraphQL query. /// /// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) /// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments #[derive(Debug)] #[must_use] pub struct LookAheadArgument<'a, S> { name: &'a Spanning<&'a str>, input_value: &'a Spanning>, vars: &'a Variables, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for LookAheadArgument<'a, S> { fn clone(&self) -> Self { *self } } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S> Copy for LookAheadArgument<'a, S> {} impl<'a, S> LookAheadArgument<'a, S> { /// Returns the name of this [argument]. /// /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments #[must_use] pub fn name(&self) -> &'a str { self.name.item } /// Returns the [`Span`] of this [argument]'s [`name`]. /// /// [`name`]: LookAheadArgument::name() /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments #[must_use] pub fn name_span(&self) -> &'a Span { &self.name.span } /// Evaluates and returns the value of this [argument]. /// /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments pub fn value(&self) -> LookAheadValue<'a, S> where S: ScalarValue, { LookAheadValue::from_input_value(self.input_value.as_ref(), Some(self.vars)).item } /// Returns the [`Span`] of this [argument]'s [`value`]. /// /// [`value`]: LookAheadArgument::value() /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments #[must_use] pub fn value_span(&self) -> &'a Span { &self.input_value.span } } /// Children of a [`LookAheadSelection`]. #[derive(Debug)] #[must_use] pub struct LookAheadChildren<'a, S> { children: Vec>, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for LookAheadChildren<'a, S> { fn clone(&self) -> Self { Self { children: self.children.clone(), } } } // Implemented manually to omit redundant `S: Default` trait bound, imposed by `#[derive(Default)]`. impl<'a, S> Default for LookAheadChildren<'a, S> { fn default() -> Self { Self { children: vec![] } } } impl<'a, S> LookAheadChildren<'a, S> { /// Returns the number of children present. #[must_use] pub fn len(&self) -> usize { self.children.len() } /// Indicates whether the current [selection] has any children. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn is_empty(&self) -> bool { self.children.is_empty() } /// Returns the child [selection] for the specified [field]. /// /// If a child has an alias, it will only match if the alias matches the specified `name`. /// /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn select(&self, name: &str) -> Option> { self.children .iter() .find(|child| child.field_name() == name) .copied() } /// Checks if the child [selection] with the specified `name` exists. /// /// If a child has an alias, it will only match if the alias matches the specified `name`. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn has_child(&self, name: &str) -> bool { self.select(name).is_some() } /// Returns the possibly aliased names of the top-level children from the current [selection]. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets pub fn names(&self) -> impl DoubleEndedIterator + '_ { self.children.iter().map(|sel| sel.field_name()) } /// Returns an [`Iterator`] over these children, by reference. pub fn iter(&self) -> impl DoubleEndedIterator> + '_ { self.children.iter() } } impl<'a, S: ScalarValue> IntoIterator for LookAheadChildren<'a, S> { type Item = LookAheadSelection<'a, S>; type IntoIter = vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.children.into_iter() } } #[derive(Debug)] pub(super) enum SelectionSource<'a, S> { Field(&'a Field<'a, S>), Spread { field_name: &'a str, set: Option<&'a [Selection<'a, S>]>, }, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for SelectionSource<'a, S> { fn clone(&self) -> Self { *self } } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S> Copy for SelectionSource<'a, S> {} /// [Selection] of an executed GraphQL query, used in [look-ahead][0] operations. /// /// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) /// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation /// [Selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[derive(Debug)] #[must_use] pub struct LookAheadSelection<'a, S> { source: SelectionSource<'a, S>, applies_for: Applies<'a>, vars: &'a Variables, fragments: &'a HashMap<&'a str, Fragment<'a, S>>, } // Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. impl<'a, S> Clone for LookAheadSelection<'a, S> { fn clone(&self) -> Self { *self } } // Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. impl<'a, S> Copy for LookAheadSelection<'a, S> {} impl<'a, S> LookAheadSelection<'a, S> { /// Constructs a new [`LookAheadSelection`] out of the provided params. pub(super) fn new( source: SelectionSource<'a, S>, vars: &'a Variables, fragments: &'a HashMap<&'a str, Fragment<'a, S>>, ) -> Self { Self { source, applies_for: Applies::All, vars, fragments, } } /// Returns the original name of the [field], represented by the current [selection]. /// /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn field_original_name(&self) -> &'a str { match self.source { SelectionSource::Field(f) => f.name.item, SelectionSource::Spread { field_name, .. } => field_name, } } /// Returns the alias of the [field], represented by the current [selection], if any is present. /// /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn field_alias(&self) -> Option<&'a str> { match self.source { SelectionSource::Field(f) => f.alias.map(|a| a.item), SelectionSource::Spread { .. } => None, } } /// Returns the potentially aliased name of the [field], represented by the current [selection]. /// /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn field_name(&self) -> &'a str { self.field_alias() .unwrap_or_else(|| self.field_original_name()) } /// Indicates whether the current [selection] has any [arguments]. /// /// [arguments]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn has_arguments(&self) -> bool { match self.source { SelectionSource::Field(f) => match &f.arguments { Some(args) => !args.item.items.is_empty(), None => false, }, _ => false, } } /// Returns an [`Iterator`] over the top-level [arguments] from the current [selection], if any /// are present. /// /// [arguments]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets pub fn arguments(&self) -> impl DoubleEndedIterator> { let opt_arguments = match self.source { SelectionSource::Field(f) => f.arguments.as_ref(), _ => None, }; opt_arguments .into_iter() .flat_map(|args| args.item.iter()) .map(|(name, arg)| LookAheadArgument { name, input_value: arg, vars: self.vars, }) } /// Returns the top-level [argument] from the current [selection] by its `name`, if any is /// present. /// /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets #[must_use] pub fn argument(&self, name: &str) -> Option> { self.arguments().find(|arg| arg.name() == name) } /// Returns the children from the current [selection]. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets pub fn children(&self) -> LookAheadChildren<'a, S> where S: ScalarValue, { self.build_children(Applies::All) } /// Returns the children from the current [selection] applying to the specified [type] only. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets /// [type]: https://spec.graphql.org/October2021#sec-Types pub fn children_for_explicit_type(&self, type_name: &str) -> LookAheadChildren<'a, S> where S: ScalarValue, { self.build_children(Applies::OnlyType(type_name)) } fn build_children(&self, type_filter: Applies) -> LookAheadChildren<'a, S> where S: ScalarValue, { let mut builder = ChildrenBuilder { vars: self.vars, fragments: self.fragments, type_filter, output: vec![], }; match &self.source { SelectionSource::Field(f) => { builder.visit_parent_field(f, Applies::All); } SelectionSource::Spread { set: Some(selections), .. } => { for s in selections.iter() { builder.visit_parent_selection(s, Applies::All); } } SelectionSource::Spread { set: None, .. } => {} } LookAheadChildren { children: builder.output, } } /// Returns the name of parent [type], in case there is any for the current [selection]. /// /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets /// [type]: https://spec.graphql.org/October2021#sec-Types #[must_use] pub fn applies_for(&self) -> Option<&str> { match self.applies_for { Applies::OnlyType(name) => Some(name), Applies::All => None, } } } struct ChildrenBuilder<'a, 'f, S> { vars: &'a Variables, fragments: &'a HashMap<&'a str, Fragment<'a, S>>, type_filter: Applies<'f>, output: Vec>, } impl<'a, 'f, S: ScalarValue> ChildrenBuilder<'a, 'f, S> { fn visit_parent_selection( &mut self, selection: &'a Selection<'a, S>, applies_for: Applies<'a>, ) { match selection { Selection::Field(f) => { self.visit_parent_field(&f.item, applies_for); } Selection::FragmentSpread(frag_sp) => { let fragment = self .fragments .get(&frag_sp.item.name.item) .expect("a fragment"); for sel in &fragment.selection_set { self.visit_parent_selection(sel, applies_for); } } Selection::InlineFragment(inl_frag) => { for sel in &inl_frag.item.selection_set { self.visit_parent_selection(sel, applies_for); } } } } fn visit_parent_field(&mut self, field: &'a Field<'a, S>, applies_for: Applies<'a>) { if let Some(selection_set) = &field.selection_set { for sel in selection_set { self.visit_child(sel, applies_for); } } } fn visit_child(&mut self, selection: &'a Selection<'a, S>, applies_for: Applies<'a>) { match selection { Selection::Field(f) => { let field = &f.item; if !self.should_include_child(field.directives.as_ref()) { return; } if let (Applies::OnlyType(type_name), Applies::OnlyType(filter)) = (applies_for, self.type_filter) { if type_name != filter { return; } } self.output.push(LookAheadSelection { source: SelectionSource::Field(field), applies_for, vars: self.vars, fragments: self.fragments, }); } Selection::FragmentSpread(frag_sp) => { if !self.should_include_child(frag_sp.item.directives.as_ref()) { return; } let fragment = self .fragments .get(&frag_sp.item.name.item) .expect("a fragment"); for sel in &fragment.selection_set { self.visit_child(sel, applies_for); } } Selection::InlineFragment(inl_frag) => { if !self.should_include_child(inl_frag.item.directives.as_ref()) { return; } let applies_for = inl_frag .item .type_condition .as_ref() .map(|name| Applies::OnlyType(name.item)) .unwrap_or(applies_for); for sel in &inl_frag.item.selection_set { self.visit_child(sel, applies_for); } } } } fn should_include_child<'b: 'a, 'c: 'a>( &self, directives: Option<&'b Vec>>>, ) -> bool { use std::ops::Not; directives .map(|d| { d.iter().all(|d| { let directive = &d.item; match (directive.name.item, &directive.arguments) { ("include", Some(args)) => args .item .items .iter() .find(|i| i.0.item == "if") .map(|(_, v)| { if let LookAheadValue::Scalar(s) = LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) .item { s.as_bool().unwrap_or(false) } else { false } }) .unwrap_or(false), ("skip", Some(args)) => args .item .items .iter() .find(|i| i.0.item == "if") .map(|(_, v)| { if let LookAheadValue::Scalar(b) = LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) .item { b.as_bool().map(Not::not).unwrap_or(false) } else { false } }) .unwrap_or(false), ("skip", &None) => false, ("include", &None) => true, (_, _) => unreachable!(), } }) }) .unwrap_or(true) } } #[cfg(test)] mod tests { use std::collections::HashMap; use crate::{ ast::{Document, OwnedDocument}, graphql_vars, parser::UnlocatedParseResult, schema::model::SchemaType, validation::test_harness::{MutationRoot, QueryRoot, SubscriptionRoot}, value::{DefaultScalarValue, ScalarValue}, }; use super::*; fn parse_document_source(q: &str) -> UnlocatedParseResult> where S: ScalarValue, { crate::parse_document_source( q, &SchemaType::new::(&(), &(), &()), ) } fn extract_fragments<'a, S>(doc: &'a Document) -> HashMap<&'a str, Fragment<'a, S>> where S: Clone, { let mut fragments = HashMap::new(); for d in doc { if let crate::ast::Definition::Fragment(ref f) = *d { let f = f.item.clone(); fragments.insert(f.name.item, f); } } fragments } fn selection_look_ahead<'a, S: ScalarValue>( selection: &'a Selection<'a, S>, vars: &'a Variables, fragments: &'a HashMap<&'a str, Fragment<'a, S>>, ) -> LookAheadSelection<'a, S> { let mut collector = ChildrenBuilder { vars, fragments, type_filter: Applies::All, output: vec![], }; collector.visit_child(selection, Applies::All); collector.output.into_iter().next().unwrap() } #[derive(Debug, PartialEq)] enum ValueDebug<'a, S: ScalarValue> { Null, Scalar(&'a S), Enum(&'a str), List(Vec>), Object(Vec<(&'a str, ValueDebug<'a, S>)>), } impl<'a, S: ScalarValue> From> for ValueDebug<'a, S> { fn from(look_ahead: LookAheadValue<'a, S>) -> Self { match look_ahead { LookAheadValue::Null => Self::Null, LookAheadValue::Scalar(s) => Self::Scalar(s), LookAheadValue::Enum(e) => Self::Enum(e), LookAheadValue::List(list) => { Self::List(list.iter().map(|val| val.item.into()).collect()) } LookAheadValue::Object(object) => Self::Object( object .iter() .map(|(key, value)| (key.item, value.item.into())) .collect(), ), } } } #[derive(Debug, PartialEq)] struct LookAheadDebug<'a, S: ScalarValue> { name: &'a str, alias: Option<&'a str>, applies_for: Applies<'a>, arguments: Option)>>, children: Vec>, } impl<'a, S: ScalarValue> LookAheadDebug<'a, S> { fn new(look_ahead: &LookAheadSelection<'a, S>) -> Self { Self::new_filtered(look_ahead, Applies::All) } fn new_filtered(look_ahead: &LookAheadSelection<'a, S>, type_filter: Applies) -> Self { Self { name: look_ahead.field_name(), alias: look_ahead.field_alias(), applies_for: look_ahead.applies_for, arguments: if look_ahead.has_arguments() { Some( look_ahead .arguments() .map(|argument| (argument.name(), ValueDebug::from(argument.value()))) .collect(), ) } else { None }, children: look_ahead .build_children(type_filter) .iter() .map(|child| Self::new_filtered(child, type_filter)) .collect(), } } } #[test] fn check_simple_query() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { id name } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, applies_for: Applies::All, arguments: None, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: vec![], applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_child() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { id name friends { name id } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "friends", alias: None, arguments: None, children: vec![ LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_argument() { let docs = parse_document_source( //language=GraphQL " query Hero { hero(episode: EMPIRE) { id name(uppercase: true) } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: Some(vec![("episode", ValueDebug::Enum("EMPIRE"))]), applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: Some(vec![( "uppercase", ValueDebug::Scalar(&DefaultScalarValue::Boolean(true)), )]), children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_variable() { let docs = parse_document_source::( //language=GraphQL " query Hero($episode: Episode) { hero(episode: $episode) { id name } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {"episode": JEDI}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: Some(vec![("episode", ValueDebug::Enum("JEDI"))]), applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_optional_variable() { let docs = parse_document_source::( //language=GraphQL " query Hero($episode: Episode) { hero(episode: $episode) { id } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: Some(vec![("episode", ValueDebug::Null)]), applies_for: Applies::All, children: vec![LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_fragment() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { id ...commonFields } } fragment commonFields on Character { name appearsIn } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "appearsIn", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_directives() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { id @include(if: true) name @include(if: false) appearsIn @skip(if: true) height @skip(if: false) } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "height", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_inline_fragments() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { name ... on Droid { primaryFunction } ... on Human { height } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "primaryFunction", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, LookAheadDebug { name: "height", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_query_with_multiple() { let docs = parse_document_source::( //language=GraphQL " query HeroAndHuman { hero { id } human { name } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); let look_ahead = selection_look_ahead(&op.item.selection_set[1], &vars, &fragments); let expected = LookAheadDebug { name: "human", alias: None, arguments: None, applies_for: Applies::All, children: vec![LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_complex_query() { let docs = parse_document_source( //language=GraphQL " query HeroNameAndFriends($id: Integer!, $withFriends: Boolean! = true) { hero(id: $id) { id ... comparisonFields friends @include(if: $withFriends) { ... comparisonFields ... on Human @skip(if: true) { mass } } } } fragment comparisonFields on Character { __typename name appearsIn ... on Droid { primaryFunction } ... on Human { height } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! { "id": 42, "withFriends": true, }; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: Some(vec![( "id", ValueDebug::Scalar(&DefaultScalarValue::Int(42)), )]), applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "__typename", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "appearsIn", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "primaryFunction", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, LookAheadDebug { name: "height", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, LookAheadDebug { name: "friends", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "__typename", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "appearsIn", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "primaryFunction", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, LookAheadDebug { name: "height", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, ], }, ], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_resolve_concrete_type() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { name ... on Droid { primaryFunction } ... on Human { height } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "height", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, ], }; assert_eq!( LookAheadDebug::new_filtered(&look_ahead, Applies::OnlyType("Human")), expected ); } else { panic!("No Operation found"); } } #[test] fn check_select_child() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { id friends { id name } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let id = look_ahead.children().select("id").unwrap(); let concrete_id = look_ahead .children_for_explicit_type("does not matter") .select("id") .unwrap(); let expected = LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }; assert_eq!(LookAheadDebug::new(&id), expected); assert_eq!(LookAheadDebug::new(&concrete_id), expected); let friends = look_ahead.children().select("friends").unwrap(); let concrete_friends = look_ahead .children_for_explicit_type("does not matter") .select("friends") .unwrap(); let expected = LookAheadDebug { name: "friends", alias: None, arguments: None, applies_for: Applies::All, children: vec![ LookAheadDebug { name: "id", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; assert_eq!(LookAheadDebug::new(&friends), expected); assert_eq!(LookAheadDebug::new(&concrete_friends), expected); } } #[test] // https://github.com/graphql-rust/juniper/issues/335 fn check_fragment_with_nesting() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { ...heroFriendNames } } fragment heroFriendNames on Hero { friends { name } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let expected = LookAheadDebug { name: "hero", alias: None, arguments: None, applies_for: Applies::All, children: vec![LookAheadDebug { name: "friends", alias: None, arguments: None, applies_for: Applies::All, children: vec![LookAheadDebug { name: "name", alias: None, arguments: None, children: Vec::new(), applies_for: Applies::All, }], }], }; assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } } #[test] fn check_visitability() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero(episode: EMPIRE) { name aliasedName: name friends { name } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); assert_eq!(look_ahead.field_original_name(), "hero"); assert!(look_ahead.field_alias().is_none()); assert_eq!(look_ahead.field_name(), "hero"); assert!(look_ahead.has_arguments()); let arg = look_ahead.arguments().next().unwrap(); assert_eq!(arg.name(), "episode"); assert_eq!(ValueDebug::from(arg.value()), ValueDebug::Enum("EMPIRE")); let children = look_ahead.children(); assert!(!children.is_empty()); assert_eq!( children.names().collect::>(), vec!["name", "aliasedName", "friends"] ); let mut child_iter = children.iter(); let name_child = child_iter.next().unwrap(); assert!(children.has_child("name")); assert_eq!( LookAheadDebug::new(name_child), LookAheadDebug::new(&children.select("name").unwrap()) ); assert_eq!(name_child.field_original_name(), "name"); assert_eq!(name_child.field_alias(), None); assert_eq!(name_child.field_name(), "name"); assert!(!name_child.has_arguments()); assert!(name_child.children().is_empty()); let aliased_name_child = child_iter.next().unwrap(); assert!(children.has_child("aliasedName")); assert_eq!( LookAheadDebug::new(aliased_name_child), LookAheadDebug::new(&children.select("aliasedName").unwrap()) ); assert_eq!(aliased_name_child.field_original_name(), "name"); assert_eq!(aliased_name_child.field_alias(), Some("aliasedName")); assert_eq!(aliased_name_child.field_name(), "aliasedName"); assert!(!aliased_name_child.has_arguments()); assert!(aliased_name_child.children().is_empty()); let friends_child = child_iter.next().unwrap(); assert!(children.has_child("friends")); assert_eq!( LookAheadDebug::new(friends_child), LookAheadDebug::new(&children.select("friends").unwrap()) ); assert_eq!(friends_child.field_original_name(), "friends"); assert_eq!(friends_child.field_alias(), None); assert_eq!(friends_child.field_name(), "friends"); assert!(!friends_child.has_arguments()); assert!(!friends_child.children().is_empty()); assert_eq!( friends_child.children().names().collect::>(), vec!["name"] ); assert!(child_iter.next().is_none()); let friends_children = friends_child.children(); let mut friends_child_iter = friends_children.iter(); let child = friends_child_iter.next().unwrap(); assert!(friends_children.has_child("name")); assert_eq!( LookAheadDebug::new(child), LookAheadDebug::new(&children.select("name").unwrap()) ); assert_eq!(child.field_original_name(), "name"); assert_eq!(child.field_alias(), None); assert_eq!(child.field_name(), "name"); assert!(!child.has_arguments()); assert!(child.children().is_empty()); assert!(friends_child_iter.next().is_none()); } else { panic!("No Operation found"); } } #[test] fn check_resolves_applies_for() { let docs = parse_document_source::( //language=GraphQL " query Hero { hero { ... on Human { height } } } ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); let mut children = look_ahead.children_for_explicit_type("Human").into_iter(); let heights_child = children.next().unwrap(); assert_eq!(heights_child.field_original_name(), "height"); assert_eq!(heights_child.applies_for, Applies::OnlyType("Human")); assert_eq!(heights_child.applies_for().unwrap(), "Human"); } else { panic!("No Operation found"); } } } juniper-0.16.2/src/executor/mod.rs000064400000000000000000001212051046102023000151410ustar 00000000000000//! Resolve the document to values use std::{ borrow::Cow, cmp::Ordering, collections::HashMap, fmt::{Debug, Display}, sync::{Arc, RwLock}, }; use fnv::FnvHashMap; use futures::Stream; use crate::{ ast::{ Definition, Document, Fragment, FromInputValue, InputValue, Operation, OperationType, Selection, ToInputValue, Type, }, parser::{SourcePosition, Spanning}, schema::{ meta::{ Argument, DeprecationStatus, EnumMeta, EnumValue, Field, InputObjectMeta, InterfaceMeta, ListMeta, MetaType, NullableMeta, ObjectMeta, PlaceholderMeta, ScalarMeta, UnionMeta, }, model::{RootNode, SchemaType, TypeType}, }, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{GraphQLType, GraphQLValue}, name::Name, subscriptions::{GraphQLSubscriptionType, GraphQLSubscriptionValue}, }, value::{DefaultScalarValue, ParseScalarValue, ScalarValue, Value}, GraphQLError, }; pub use self::{ look_ahead::{ Applies, LookAheadArgument, LookAheadChildren, LookAheadList, LookAheadObject, LookAheadSelection, LookAheadValue, }, owned_executor::OwnedExecutor, }; mod look_ahead; mod owned_executor; /// A type registry used to build schemas /// /// The registry gathers metadata for all types in a schema. It provides /// convenience methods to convert types implementing the `GraphQLType` trait /// into `Type` instances and automatically registers them. pub struct Registry<'r, S = DefaultScalarValue> { /// Currently registered types pub types: FnvHashMap>, } #[allow(missing_docs)] #[derive(Clone)] pub enum FieldPath<'a> { Root(SourcePosition), Field(&'a str, SourcePosition, Arc>), } /// Query execution engine /// /// The executor helps drive the query execution in a schema. It keeps track /// of the current field stack, context, variables, and errors. pub struct Executor<'r, 'a, CtxT, S = DefaultScalarValue> where CtxT: 'a, S: 'a, { fragments: &'r HashMap<&'a str, Fragment<'a, S>>, variables: &'r Variables, current_selection_set: Option<&'r [Selection<'a, S>]>, parent_selection_set: Option<&'r [Selection<'a, S>]>, current_type: TypeType<'a, S>, schema: &'a SchemaType<'a, S>, context: &'a CtxT, errors: &'r RwLock>>, field_path: Arc>, } /// Error type for errors that occur during query execution /// /// All execution errors contain the source position in the query of the field /// that failed to resolve. It also contains the field stack. #[derive(Clone, Debug, PartialEq)] pub struct ExecutionError { location: SourcePosition, path: Vec, error: FieldError, } impl Eq for ExecutionError where Self: PartialEq {} impl ExecutionError { /// Construct a new execution error occuring at the beginning of the query pub fn at_origin(error: FieldError) -> ExecutionError { ExecutionError { location: SourcePosition::new_origin(), path: Vec::new(), error, } } } impl PartialOrd for ExecutionError where Self: PartialEq, { fn partial_cmp(&self, other: &ExecutionError) -> Option { Some(self.cmp(other)) } } impl Ord for ExecutionError where Self: Eq, { fn cmp(&self, other: &ExecutionError) -> Ordering { (&self.location, &self.path, &self.error.message).cmp(&( &other.location, &other.path, &other.error.message, )) } } /// Error type for errors that occur during field resolution /// /// Field errors are represented by a human-readable error message and an /// optional `Value` structure containing additional information. /// /// They can be converted to from any type that implements `std::fmt::Display`, /// which makes error chaining with the `?` operator a breeze: /// /// ```rust /// # use juniper::{FieldError, ScalarValue}; /// fn get_string(data: Vec) -> Result /// { /// let s = String::from_utf8(data)?; /// Ok(s) /// } /// ``` #[derive(Clone, Debug, PartialEq)] pub struct FieldError { message: String, extensions: Value, } impl From for FieldError { fn from(e: T) -> Self { Self { message: e.to_string(), extensions: Value::Null, } } } impl FieldError { /// Construct a new [`FieldError`] with additional data. /// /// You can use the [`graphql_value!`] macro for construction: /// ```rust /// use juniper::{graphql_value, FieldError}; /// /// # let _: FieldError = /// FieldError::new( /// "Could not open connection to the database", /// graphql_value!({"internal_error": "Connection refused"}), /// ); /// ``` /// /// The `extensions` parameter will be added to the `"extensions"` field of /// the `"errors"` object in response: /// ```json /// { /// "errors": [ /// "message": "Could not open connection to the database", /// "locations": [{"line": 2, "column": 4}], /// "extensions": { /// "internal_error": "Connection refused" /// } /// ] /// } /// ``` /// /// If the argument is [`Value::Null`], then no extra data will be included. /// /// [`graphql_value!`]: macro@crate::graphql_value #[must_use] pub fn new(e: T, extensions: Value) -> Self { Self { message: e.to_string(), extensions, } } /// Returns `"message"` field of this [`FieldError`]. #[must_use] pub fn message(&self) -> &str { &self.message } /// Returns `"extensions"` field of this [`FieldError`]. /// /// If there is no `"extensions"`, then [`Value::Null`] will be returned. #[must_use] pub fn extensions(&self) -> &Value { &self.extensions } /// Maps the [`ScalarValue`] type of this [`FieldError`] into the specified /// one. #[must_use] pub fn map_scalar_value(self) -> FieldError where S: ScalarValue, Into: ScalarValue, { FieldError { message: self.message, extensions: self.extensions.map_scalar_value(), } } /// Maps the [`FieldError::message`] with the given function. #[must_use] pub fn map_message(self, f: impl FnOnce(String) -> String) -> Self { Self { message: f(self.message), extensions: self.extensions, } } } /// The result of resolving the value of a field of type `T` pub type FieldResult = Result>; /// The result of resolving an unspecified field pub type ExecutionResult = Result, FieldError>; /// Boxed `Stream` yielding `Result, ExecutionError>` pub type ValuesStream<'a, S = DefaultScalarValue> = std::pin::Pin, ExecutionError>> + Send + 'a>>; /// The map of variables used for substitution during query execution pub type Variables = HashMap>; /// Custom error handling trait to enable error types other than [`FieldError`] /// to be specified as return value. /// /// Any custom error type should implement this trait to convert itself into a /// [`FieldError`]. pub trait IntoFieldError { /// Performs the custom conversion into a [`FieldError`]. #[must_use] fn into_field_error(self) -> FieldError; } impl IntoFieldError for FieldError { fn into_field_error(self) -> FieldError { self.map_scalar_value() } } impl IntoFieldError for std::convert::Infallible { fn into_field_error(self) -> FieldError { match self {} } } impl<'a, S> IntoFieldError for &'a str { fn into_field_error(self) -> FieldError { FieldError::::from(self) } } impl IntoFieldError for String { fn into_field_error(self) -> FieldError { FieldError::::from(self) } } impl<'a, S> IntoFieldError for Cow<'a, str> { fn into_field_error(self) -> FieldError { FieldError::::from(self) } } #[doc(hidden)] pub trait IntoResolvable<'a, S, T, C> where T: GraphQLValue, S: ScalarValue, { type Type; #[doc(hidden)] fn into_resolvable(self, ctx: &'a C) -> FieldResult, S>; } impl<'a, S, T, C> IntoResolvable<'a, S, T, C> for T where T: GraphQLValue, S: ScalarValue, T::Context: FromContext, { type Type = T; fn into_resolvable(self, ctx: &'a C) -> FieldResult, S> { Ok(Some((FromContext::from(ctx), self))) } } impl<'a, S, T, C, E: IntoFieldError> IntoResolvable<'a, S, T, C> for Result where S: ScalarValue, T: GraphQLValue, T::Context: FromContext, { type Type = T; fn into_resolvable(self, ctx: &'a C) -> FieldResult, S> { self.map(|v: T| Some((>::from(ctx), v))) .map_err(IntoFieldError::into_field_error) } } impl<'a, S, T, C> IntoResolvable<'a, S, T, C> for (&'a T::Context, T) where S: ScalarValue, T: GraphQLValue, { type Type = T; fn into_resolvable(self, _: &'a C) -> FieldResult, S> { Ok(Some(self)) } } impl<'a, S, T, C> IntoResolvable<'a, S, Option, C> for Option<(&'a T::Context, T)> where S: ScalarValue, T: GraphQLValue, { type Type = T; #[allow(clippy::type_complexity)] fn into_resolvable(self, _: &'a C) -> FieldResult)>, S> { Ok(self.map(|(ctx, v)| (ctx, Some(v)))) } } impl<'a, S1, S2, T, C> IntoResolvable<'a, S2, T, C> for FieldResult<(&'a T::Context, T), S1> where S1: ScalarValue, S2: ScalarValue, T: GraphQLValue, { type Type = T; fn into_resolvable(self, _: &'a C) -> FieldResult, S2> { self.map(Some).map_err(FieldError::map_scalar_value) } } impl<'a, S1, S2, T, C> IntoResolvable<'a, S2, Option, C> for FieldResult, S1> where S1: ScalarValue, S2: ScalarValue, T: GraphQLValue, { type Type = T; #[allow(clippy::type_complexity)] fn into_resolvable(self, _: &'a C) -> FieldResult)>, S2> { self.map(|o| o.map(|(ctx, v)| (ctx, Some(v)))) .map_err(FieldError::map_scalar_value) } } /// Conversion trait for context types /// /// Used to support different context types for different parts of an /// application. By making each `GraphQL` type only aware of as much /// context as it needs to, isolation and robustness can be /// improved. Implement this trait if you have contexts that can /// generally be converted between each other. /// /// The empty tuple `()` can be converted into from any context type, /// making it suitable for `GraphQL` that don't need _any_ context to /// work, e.g. scalars or enums. pub trait FromContext { /// Perform the conversion fn from(value: &T) -> &Self; } /// Marker trait for types that can act as context objects for `GraphQL` types. pub trait Context {} impl<'a, C: Context> Context for &'a C {} static NULL_CONTEXT: () = (); impl FromContext for () { fn from(_: &T) -> &Self { &NULL_CONTEXT } } impl FromContext for T where T: Context, { fn from(value: &T) -> &Self { value } } impl<'r, 'a, CtxT, S> Executor<'r, 'a, CtxT, S> where S: ScalarValue, { /// Resolve a single arbitrary value into a stream of [`Value`]s. /// If a field fails to resolve, pushes error to `Executor` /// and returns `Value::Null`. pub async fn resolve_into_stream<'i, 'v, 'res, T>( &'r self, info: &'i T::TypeInfo, value: &'v T, ) -> Value> where 'i: 'res, 'v: 'res, 'a: 'res, T: GraphQLSubscriptionValue + ?Sized, T::TypeInfo: Sync, CtxT: Sync, S: Send + Sync, { self.subscribe(info, value).await.unwrap_or_else(|e| { self.push_error(e); Value::Null }) } /// Resolve a single arbitrary value into a stream of [`Value`]s. /// Calls `resolve_into_stream` on `T`. pub async fn subscribe<'s, 't, 'res, T>( &'r self, info: &'t T::TypeInfo, value: &'t T, ) -> Result>, FieldError> where 't: 'res, 'a: 'res, T: GraphQLSubscriptionValue + ?Sized, T::TypeInfo: Sync, CtxT: Sync, S: Send + Sync, { value.resolve_into_stream(info, self).await } /// Resolve a single arbitrary value, mapping the context to a new type pub fn resolve_with_ctx(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult where NewCtxT: FromContext, T: GraphQLValue + ?Sized, { self.replaced_context(>::from(self.context)) .resolve(info, value) } /// Resolve a single arbitrary value into an `ExecutionResult` pub fn resolve(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult where T: GraphQLValue + ?Sized, { value.resolve(info, self.current_selection_set, self) } /// Resolve a single arbitrary value into an `ExecutionResult` pub async fn resolve_async(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, CtxT: Sync, S: Send + Sync, { value .resolve_async(info, self.current_selection_set, self) .await } /// Resolve a single arbitrary value, mapping the context to a new type pub async fn resolve_with_ctx_async( &self, info: &T::TypeInfo, value: &T, ) -> ExecutionResult where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, NewCtxT: FromContext + Sync, S: Send + Sync, { let e = self.replaced_context(>::from(self.context)); e.resolve_async(info, value).await } /// Resolve a single arbitrary value into a return value /// /// If the field fails to resolve, `null` will be returned. pub fn resolve_into_value(&self, info: &T::TypeInfo, value: &T) -> Value where T: GraphQLValue, { self.resolve(info, value).unwrap_or_else(|e| { self.push_error(e); Value::null() }) } /// Resolve a single arbitrary value into a return value /// /// If the field fails to resolve, `null` will be returned. pub async fn resolve_into_value_async(&self, info: &T::TypeInfo, value: &T) -> Value where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, CtxT: Sync, S: Send + Sync, { self.resolve_async(info, value).await.unwrap_or_else(|e| { self.push_error(e); Value::null() }) } /// Derive a new executor by replacing the context /// /// This can be used to connect different types, e.g. from different Rust /// libraries, that require different context types. pub fn replaced_context<'b, NewCtxT>( &'b self, ctx: &'b NewCtxT, ) -> Executor<'b, 'b, NewCtxT, S> { Executor { fragments: self.fragments, variables: self.variables, current_selection_set: self.current_selection_set, parent_selection_set: self.parent_selection_set, current_type: self.current_type.clone(), schema: self.schema, context: ctx, errors: self.errors, field_path: self.field_path.clone(), } } #[doc(hidden)] pub fn field_sub_executor<'s>( &'s self, field_alias: &'a str, field_name: &'s str, location: SourcePosition, selection_set: Option<&'s [Selection<'a, S>]>, ) -> Executor<'s, 'a, CtxT, S> { Executor { fragments: self.fragments, variables: self.variables, current_selection_set: selection_set, parent_selection_set: self.current_selection_set, current_type: self.schema.make_type( &self .current_type .innermost_concrete() .field_by_name(field_name) .expect("Field not found on inner type") .field_type, ), schema: self.schema, context: self.context, errors: self.errors, field_path: Arc::new(FieldPath::Field( field_alias, location, Arc::clone(&self.field_path), )), } } #[doc(hidden)] pub fn type_sub_executor<'s>( &'s self, type_name: Option<&'s str>, selection_set: Option<&'s [Selection<'a, S>]>, ) -> Executor<'s, 'a, CtxT, S> { Executor { fragments: self.fragments, variables: self.variables, current_selection_set: selection_set, parent_selection_set: self.current_selection_set, current_type: match type_name { Some(type_name) => self.schema.type_by_name(type_name).expect("Type not found"), None => self.current_type.clone(), }, schema: self.schema, context: self.context, errors: self.errors, field_path: self.field_path.clone(), } } /// `Executor`'s current selection set pub(crate) fn current_selection_set(&self) -> Option<&[Selection<'a, S>]> { self.current_selection_set } /// Access the current context /// /// You usually provide the context when calling the top-level `execute` /// function, or using the context factory. pub fn context(&self) -> &'r CtxT { self.context } /// The currently executing schema pub fn schema(&self) -> &'a SchemaType { self.schema } #[doc(hidden)] pub fn current_type(&self) -> &TypeType<'a, S> { &self.current_type } #[doc(hidden)] pub fn variables(&self) -> &'r Variables { self.variables } #[doc(hidden)] pub fn fragment_by_name<'s>(&'s self, name: &str) -> Option<&'s Fragment<'a, S>> { self.fragments.get(name) } /// The current location of the executor pub fn location(&self) -> &SourcePosition { self.field_path.location() } /// Add an error to the execution engine at the current executor location pub fn push_error(&self, error: FieldError) { self.push_error_at(error, *self.location()); } /// Add an error to the execution engine at a specific location pub fn push_error_at(&self, error: FieldError, location: SourcePosition) { let mut path = Vec::new(); self.field_path.construct_path(&mut path); let mut errors = self.errors.write().unwrap(); errors.push(ExecutionError { location, path, error, }); } /// Returns new [`ExecutionError`] at current location pub fn new_error(&self, error: FieldError) -> ExecutionError { let mut path = Vec::new(); self.field_path.construct_path(&mut path); ExecutionError { location: *self.location(), path, error, } } /// Construct a lookahead selection for the current selection. /// /// This allows seeing the whole selection and perform operations /// affecting the children. pub fn look_ahead(&'a self) -> LookAheadSelection<'a, S> { let field_name = match *self.field_path { FieldPath::Field(x, ..) => x, FieldPath::Root(_) => unreachable!(), }; self.parent_selection_set .and_then(|p| { // Search the parent's fields to find this field within the selection set. p.iter().find_map(|x| { match x { Selection::Field(ref field) => { let field = &field.item; // TODO: support excludes. let name = field.name.item; let alias = field.alias.as_ref().map(|a| a.item); (alias.unwrap_or(name) == field_name).then(|| { LookAheadSelection::new( look_ahead::SelectionSource::Field(field), self.variables, self.fragments, ) }) } Selection::FragmentSpread(_) | Selection::InlineFragment(_) => None, } }) }) .unwrap_or_else(|| { // We didn't find this field in the parent's selection matching it, which means // we're inside a `FragmentSpread`. LookAheadSelection::new( look_ahead::SelectionSource::Spread { field_name, set: self.current_selection_set, }, self.variables, self.fragments, ) }) } /// Create new `OwnedExecutor` and clone all current data /// (except for errors) there /// /// New empty vector is created for `errors` because /// existing errors won't be needed to be accessed by user /// in OwnedExecutor as existing errors will be returned in /// `execute_query`/`execute_mutation`/`resolve_into_stream`/etc. pub fn as_owned_executor(&self) -> OwnedExecutor<'a, CtxT, S> { OwnedExecutor { fragments: self.fragments.clone(), variables: self.variables.clone(), current_selection_set: self.current_selection_set.map(|x| x.to_vec()), parent_selection_set: self.parent_selection_set.map(|x| x.to_vec()), current_type: self.current_type.clone(), schema: self.schema, context: self.context, errors: RwLock::new(vec![]), field_path: Arc::clone(&self.field_path), } } } impl<'a> FieldPath<'a> { fn construct_path(&self, acc: &mut Vec) { match self { FieldPath::Root(_) => (), FieldPath::Field(name, _, parent) => { parent.construct_path(acc); acc.push((*name).into()); } } } fn location(&self) -> &SourcePosition { match *self { FieldPath::Root(ref pos) | FieldPath::Field(_, ref pos, _) => pos, } } } impl ExecutionError { #[doc(hidden)] pub fn new(location: SourcePosition, path: &[&str], error: FieldError) -> ExecutionError { ExecutionError { location, path: path.iter().map(|s| (*s).into()).collect(), error, } } /// The error message pub fn error(&self) -> &FieldError { &self.error } /// The source location _in the query_ of the field that failed to resolve pub fn location(&self) -> &SourcePosition { &self.location } /// The path of fields leading to the field that generated this error pub fn path(&self) -> &[String] { &self.path } } /// Create new `Executor` and start query/mutation execution. /// Returns `IsSubscription` error if subscription is passed. pub fn execute_validated_query<'b, QueryT, MutationT, SubscriptionT, S>( document: &'b Document, operation: &'b Spanning>, root_node: &RootNode, variables: &Variables, context: &QueryT::Context, ) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { if operation.item.operation_type == OperationType::Subscription { return Err(GraphQLError::IsSubscription); } let mut fragments = vec![]; for def in document.iter() { if let Definition::Fragment(f) = def { fragments.push(f) }; } let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| { defs.item .items .iter() .filter_map(|(name, def)| { def.default_value .as_ref() .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); let errors = RwLock::new(Vec::new()); let value; { let mut all_vars; let mut final_vars = variables; if let Some(defaults) = default_variable_values { all_vars = variables.clone(); for (name, value) in defaults { all_vars.entry(name).or_insert(value); } final_vars = &all_vars; } let root_type = match operation.item.operation_type { OperationType::Query => root_node.schema.query_type(), OperationType::Mutation => root_node .schema .mutation_type() .expect("No mutation type found"), OperationType::Subscription => unreachable!(), }; let executor = Executor { fragments: &fragments .iter() .map(|f| (f.item.name.item, f.item.clone())) .collect(), variables: final_vars, current_selection_set: Some(&operation.item.selection_set[..]), parent_selection_set: None, current_type: root_type, schema: &root_node.schema, context, errors: &errors, field_path: Arc::new(FieldPath::Root(operation.span.start)), }; value = match operation.item.operation_type { OperationType::Query => executor.resolve_into_value(&root_node.query_info, &root_node), OperationType::Mutation => { executor.resolve_into_value(&root_node.mutation_info, &root_node.mutation_type) } OperationType::Subscription => unreachable!(), }; } let mut errors = errors.into_inner().unwrap(); errors.sort(); Ok((value, errors)) } /// Create new `Executor` and start asynchronous query execution. /// Returns `IsSubscription` error if subscription is passed. pub async fn execute_validated_query_async<'a, 'b, QueryT, MutationT, SubscriptionT, S>( document: &'b Document<'a, S>, operation: &'b Spanning>, root_node: &RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &QueryT::Context, ) -> Result<(Value, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLType + Sync, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { if operation.item.operation_type == OperationType::Subscription { return Err(GraphQLError::IsSubscription); } let mut fragments = vec![]; for def in document.iter() { if let Definition::Fragment(f) = def { fragments.push(f) }; } let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| { defs.item .items .iter() .filter_map(|(name, def)| { def.default_value .as_ref() .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); let errors = RwLock::new(Vec::new()); let value; { let mut all_vars; let mut final_vars = variables; if let Some(defaults) = default_variable_values { all_vars = variables.clone(); for (name, value) in defaults { all_vars.entry(name).or_insert(value); } final_vars = &all_vars; } let root_type = match operation.item.operation_type { OperationType::Query => root_node.schema.query_type(), OperationType::Mutation => root_node .schema .mutation_type() .expect("No mutation type found"), OperationType::Subscription => unreachable!(), }; let executor = Executor { fragments: &fragments .iter() .map(|f| (f.item.name.item, f.item.clone())) .collect(), variables: final_vars, current_selection_set: Some(&operation.item.selection_set[..]), parent_selection_set: None, current_type: root_type, schema: &root_node.schema, context, errors: &errors, field_path: Arc::new(FieldPath::Root(operation.span.start)), }; value = match operation.item.operation_type { OperationType::Query => { executor .resolve_into_value_async(&root_node.query_info, &root_node) .await } OperationType::Mutation => { executor .resolve_into_value_async(&root_node.mutation_info, &root_node.mutation_type) .await } OperationType::Subscription => unreachable!(), }; } let mut errors = errors.into_inner().unwrap(); errors.sort(); Ok((value, errors)) } #[doc(hidden)] pub fn get_operation<'b, 'd, S>( document: &'b Document<'d, S>, operation_name: Option<&str>, ) -> Result<&'b Spanning>, GraphQLError> where S: ScalarValue, { let mut operation = None; for def in document { if let Definition::Operation(op) = def { if operation_name.is_none() && operation.is_some() { return Err(GraphQLError::MultipleOperationsProvided); } let move_op = operation_name.is_none() || op.item.name.as_ref().map(|s| s.item) == operation_name; if move_op { operation = Some(op); } }; } let op = match operation { Some(op) => op, None => return Err(GraphQLError::UnknownOperationName), }; Ok(op) } /// Initialize new `Executor` and start resolving subscription into stream /// asynchronously. /// Returns `NotSubscription` error if query or mutation is passed pub async fn resolve_validated_subscription< 'r, 'exec_ref, 'd, 'op, QueryT, MutationT, SubscriptionT, S, >( document: &Document<'d, S>, operation: &Spanning>, root_node: &'r RootNode<'r, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &'r QueryT::Context, ) -> Result<(Value>, Vec>), GraphQLError> where 'r: 'exec_ref, 'd: 'r, 'op: 'd, QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync + 'r, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLSubscriptionType, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { if operation.item.operation_type != OperationType::Subscription { return Err(GraphQLError::NotSubscription); } let mut fragments = vec![]; for def in document.iter() { if let Definition::Fragment(f) = def { fragments.push(f) } } let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| { defs.item .items .iter() .filter_map(|(name, def)| { def.default_value .as_ref() .map(|i| (name.item.into(), i.item.clone())) }) .collect::>>() }); let errors = RwLock::new(Vec::new()); let value; { let mut all_vars; let mut final_vars = variables; if let Some(defaults) = default_variable_values { all_vars = variables.clone(); for (name, value) in defaults { all_vars.entry(name).or_insert(value); } final_vars = &all_vars; } let root_type = match operation.item.operation_type { OperationType::Subscription => root_node .schema .subscription_type() .expect("No subscription type found"), _ => unreachable!(), }; let executor: Executor<'_, 'r, _, _> = Executor { fragments: &fragments .iter() .map(|f| (f.item.name.item, f.item.clone())) .collect(), variables: final_vars, current_selection_set: Some(&operation.item.selection_set[..]), parent_selection_set: None, current_type: root_type, schema: &root_node.schema, context, errors: &errors, field_path: Arc::new(FieldPath::Root(operation.span.start)), }; value = match operation.item.operation_type { OperationType::Subscription => { executor .resolve_into_stream(&root_node.subscription_info, &root_node.subscription_type) .await } _ => unreachable!(), }; } let mut errors = errors.into_inner().unwrap(); errors.sort(); Ok((value, errors)) } impl<'r, S: 'r> Registry<'r, S> { /// Constructs a new [`Registry`] out of the given `types`. pub fn new(types: FnvHashMap>) -> Self { Self { types } } /// Returns a [`Type`] instance for the given [`GraphQLType`], registered in /// this [`Registry`]. /// /// If this [`Registry`] hasn't seen a [`Type`] with such /// [`GraphQLType::name`] before, it will construct the one and store it. pub fn get_type(&mut self, info: &T::TypeInfo) -> Type<'r> where T: GraphQLType + ?Sized, S: ScalarValue, { if let Some(name) = T::name(info) { let validated_name = name.parse::().unwrap(); if !self.types.contains_key(name) { self.insert_placeholder( validated_name.clone(), Type::NonNullNamed(Cow::Owned(name.into())), ); let meta = T::meta(info, self); self.types.insert(validated_name, meta); } self.types[name].as_type() } else { T::meta(info, self).as_type() } } /// Creates a [`Field`] with the provided `name`. pub fn field(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S> where T: GraphQLType + ?Sized, S: ScalarValue, { Field { name: smartstring::SmartString::from(name), description: None, arguments: None, field_type: self.get_type::(info), deprecation_status: DeprecationStatus::Current, } } #[doc(hidden)] pub fn field_convert<'a, T: IntoResolvable<'a, S, I, C>, I, C>( &mut self, name: &str, info: &I::TypeInfo, ) -> Field<'r, S> where I: GraphQLType, S: ScalarValue, { Field { name: name.into(), description: None, arguments: None, field_type: self.get_type::(info), deprecation_status: DeprecationStatus::Current, } } /// Creates an [`Argument`] with the provided `name`. pub fn arg(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S> where T: GraphQLType + FromInputValue, S: ScalarValue, { Argument::new(name, self.get_type::(info)) } /// Creates an [`Argument`] with the provided default `value`. pub fn arg_with_default( &mut self, name: &str, value: &T, info: &T::TypeInfo, ) -> Argument<'r, S> where T: GraphQLType + ToInputValue + FromInputValue, S: ScalarValue, { Argument::new(name, self.get_type::(info)).default_value(value.to_input_value()) } fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) { self.types .entry(name) .or_insert(MetaType::Placeholder(PlaceholderMeta { of_type })); } /// Creates a [`ScalarMeta`] type. pub fn build_scalar_type(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S> where T: GraphQLType + FromInputValue + ParseScalarValue, T::Error: IntoFieldError, S: ScalarValue, { let name = T::name(info).expect("Scalar types must be named. Implement `name()`"); ScalarMeta::new::(Cow::Owned(name.into())) } /// Creates a [`ListMeta`] type. /// /// Specifying `expected_size` will be used to ensure that values of this /// type will always match it. pub fn build_list_type( &mut self, info: &T::TypeInfo, expected_size: Option, ) -> ListMeta<'r> where T: GraphQLType + ?Sized, S: ScalarValue, { let of_type = self.get_type::(info); ListMeta::new(of_type, expected_size) } /// Creates a [`NullableMeta`] type. pub fn build_nullable_type(&mut self, info: &T::TypeInfo) -> NullableMeta<'r> where T: GraphQLType + ?Sized, S: ScalarValue, { let of_type = self.get_type::(info); NullableMeta::new(of_type) } /// Creates an [`ObjectMeta`] type with the given `fields`. pub fn build_object_type( &mut self, info: &T::TypeInfo, fields: &[Field<'r, S>], ) -> ObjectMeta<'r, S> where T: GraphQLType + ?Sized, S: ScalarValue, { let name = T::name(info).expect("Object types must be named. Implement name()"); let mut v = fields.to_vec(); v.push(self.field::("__typename", &())); ObjectMeta::new(Cow::Owned(name.into()), &v) } /// Creates an [`EnumMeta`] type out of the provided `values`. pub fn build_enum_type( &mut self, info: &T::TypeInfo, values: &[EnumValue], ) -> EnumMeta<'r, S> where T: GraphQLType + FromInputValue, T::Error: IntoFieldError, S: ScalarValue, { let name = T::name(info).expect("Enum types must be named. Implement `name()`"); EnumMeta::new::(Cow::Owned(name.into()), values) } /// Creates an [`InterfaceMeta`] type with the given `fields`. pub fn build_interface_type( &mut self, info: &T::TypeInfo, fields: &[Field<'r, S>], ) -> InterfaceMeta<'r, S> where T: GraphQLType + ?Sized, S: ScalarValue, { let name = T::name(info).expect("Interface types must be named. Implement name()"); let mut v = fields.to_vec(); v.push(self.field::("__typename", &())); InterfaceMeta::new(Cow::Owned(name.into()), &v) } /// Creates an [`UnionMeta`] type of the given `types`. pub fn build_union_type(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r> where T: GraphQLType + ?Sized, S: ScalarValue, { let name = T::name(info).expect("Union types must be named. Implement name()"); UnionMeta::new(Cow::Owned(name.into()), types) } /// Creates an [`InputObjectMeta`] type with the given `args`. pub fn build_input_object_type( &mut self, info: &T::TypeInfo, args: &[Argument<'r, S>], ) -> InputObjectMeta<'r, S> where T: GraphQLType + FromInputValue, T::Error: IntoFieldError, S: ScalarValue, { let name = T::name(info).expect("Input object types must be named. Implement name()"); InputObjectMeta::new::(Cow::Owned(name.into()), args) } } juniper-0.16.2/src/executor/owned_executor.rs000064400000000000000000000107331046102023000174170ustar 00000000000000use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use crate::{ ast::Fragment, executor::FieldPath, parser::SourcePosition, schema::model::{SchemaType, TypeType}, ExecutionError, Executor, Selection, Variables, }; /// [`Executor`] owning all its variables. Can be used after [`Executor`] was /// destroyed. pub struct OwnedExecutor<'a, CtxT, S> { pub(super) fragments: HashMap<&'a str, Fragment<'a, S>>, pub(super) variables: Variables, pub(super) current_selection_set: Option>>, pub(super) parent_selection_set: Option>>, pub(super) current_type: TypeType<'a, S>, pub(super) schema: &'a SchemaType<'a, S>, pub(super) context: &'a CtxT, pub(super) errors: RwLock>>, pub(super) field_path: Arc>, } impl<'a, CtxT, S> Clone for OwnedExecutor<'a, CtxT, S> where S: Clone, { fn clone(&self) -> Self { Self { fragments: self.fragments.clone(), variables: self.variables.clone(), current_selection_set: self.current_selection_set.clone(), parent_selection_set: self.parent_selection_set.clone(), current_type: self.current_type.clone(), schema: self.schema, context: self.context, errors: RwLock::new(vec![]), field_path: self.field_path.clone(), } } } impl<'a, CtxT, S> OwnedExecutor<'a, CtxT, S> where S: Clone, { #[doc(hidden)] #[must_use] pub fn type_sub_executor( &self, type_name: Option<&str>, selection_set: Option>>, ) -> OwnedExecutor<'a, CtxT, S> { OwnedExecutor { fragments: self.fragments.clone(), variables: self.variables.clone(), current_selection_set: selection_set, parent_selection_set: self.current_selection_set.clone(), current_type: match type_name { Some(type_name) => self.schema.type_by_name(type_name).expect("Type not found"), None => self.current_type.clone(), }, schema: self.schema, context: self.context, errors: RwLock::new(vec![]), field_path: self.field_path.clone(), } } #[doc(hidden)] pub fn variables(&self) -> Variables { self.variables.clone() } #[doc(hidden)] #[must_use] pub fn field_sub_executor( &self, field_alias: &'a str, field_name: &'a str, location: SourcePosition, selection_set: Option>>, ) -> OwnedExecutor<'a, CtxT, S> { OwnedExecutor { fragments: self.fragments.clone(), variables: self.variables.clone(), current_selection_set: selection_set, parent_selection_set: self.current_selection_set.clone(), current_type: self.schema.make_type( &self .current_type .innermost_concrete() .field_by_name(field_name) .expect("Field not found on inner type") .field_type, ), schema: self.schema, context: self.context, errors: RwLock::new(vec![]), field_path: Arc::new(FieldPath::Field( field_alias, location, Arc::clone(&self.field_path), )), } } #[doc(hidden)] pub fn as_executor(&self) -> Executor<'_, '_, CtxT, S> { Executor { fragments: &self.fragments, variables: &self.variables, current_selection_set: self.current_selection_set.as_deref(), parent_selection_set: self.parent_selection_set.as_deref(), current_type: self.current_type.clone(), schema: self.schema, context: self.context, errors: &self.errors, field_path: Arc::clone(&self.field_path), } } } impl<'a, CtxT, S> OwnedExecutor<'a, CtxT, S> { #[doc(hidden)] pub fn fragment_by_name<'b>(&'b self, name: &str) -> Option<&'b Fragment<'a, S>> { self.fragments.get(name) } #[doc(hidden)] pub fn context(&self) -> &'a CtxT { self.context } #[doc(hidden)] pub fn schema(&self) -> &'a SchemaType { self.schema } #[doc(hidden)] pub fn location(&self) -> &SourcePosition { self.field_path.location() } } juniper-0.16.2/src/executor_tests/async_await/mod.rs000064400000000000000000000041731046102023000206710ustar 00000000000000use crate::{ graphql_object, graphql_value, graphql_vars, EmptyMutation, EmptySubscription, GraphQLEnum, RootNode, Value, }; #[derive(GraphQLEnum)] enum UserKind { Admin, User, Guest, } struct User { #[allow(dead_code)] id: i32, name: String, kind: UserKind, } #[graphql_object] impl User { async fn id(&self) -> i32 { self.id } async fn name(&self) -> &str { &self.name } async fn friends(&self) -> Vec { (0..10) .map(|index| User { id: index, name: format!("user{index}"), kind: UserKind::User, }) .collect() } async fn kind(&self) -> &UserKind { &self.kind } async fn delayed() -> bool { tokio::time::sleep(std::time::Duration::from_millis(100)).await; true } } struct Query; #[graphql_object] impl Query { fn field_sync(&self) -> &'static str { "field_sync" } async fn field_async_plain() -> String { "field_async_plain".into() } fn user(id: String) -> User { User { id: 1, name: id, kind: UserKind::User, } } async fn delayed() -> bool { tokio::time::sleep(std::time::Duration::from_millis(100)).await; true } } #[tokio::test] async fn async_simple() { let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new()); let doc = r#" query { fieldSync fieldAsyncPlain delayed user(id: "user1") { name } } "#; let (res, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .unwrap(); assert!(errs.is_empty()); let obj = res.into_object().unwrap(); let value = Value::Object(obj); assert_eq!( value, graphql_value!({ "delayed": true, "fieldAsyncPlain": "field_async_plain", "fieldSync": "field_sync", "user": { "name": "user1", }, }), ); } juniper-0.16.2/src/executor_tests/directives.rs000064400000000000000000000157001046102023000177470ustar 00000000000000use crate::{ executor::Variables, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object}, }; struct TestType; #[crate::graphql_object] impl TestType { fn a() -> &'static str { "a" } fn b() -> &'static str { "b" } } async fn run_variable_query(query: &str, vars: Variables, f: F) where F: Fn(&Object), { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(query, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let obj = result.as_object_value().expect("Result is not an object"); f(obj); } async fn run_query(query: &str, f: F) where F: Fn(&Object), { run_variable_query(query, Variables::new(), f).await; } #[tokio::test] async fn scalar_include_true() { run_query("{ a, b @include(if: true) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn scalar_include_false() { run_query("{ a, b @include(if: false) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn scalar_skip_false() { run_query("{ a, b @skip(if: false) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn scalar_skip_true() { run_query("{ a, b @skip(if: true) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn fragment_spread_include_true() { run_query( "{ a, ...Frag @include(if: true) } fragment Frag on TestType { b }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }, ) .await; } #[tokio::test] async fn fragment_spread_include_false() { run_query( "{ a, ...Frag @include(if: false) } fragment Frag on TestType { b }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }, ) .await; } #[tokio::test] async fn fragment_spread_skip_false() { run_query( "{ a, ...Frag @skip(if: false) } fragment Frag on TestType { b }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }, ) .await; } #[tokio::test] async fn fragment_spread_skip_true() { run_query( "{ a, ...Frag @skip(if: true) } fragment Frag on TestType { b }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }, ) .await; } #[tokio::test] async fn inline_fragment_include_true() { run_query( "{ a, ... on TestType @include(if: true) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }, ) .await; } #[tokio::test] async fn inline_fragment_include_false() { run_query( "{ a, ... on TestType @include(if: false) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }, ) .await; } #[tokio::test] async fn inline_fragment_skip_false() { run_query("{ a, ... on TestType @skip(if: false) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn inline_fragment_skip_true() { run_query("{ a, ... on TestType @skip(if: true) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn anonymous_inline_fragment_include_true() { run_query("{ a, ... @include(if: true) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn anonymous_inline_fragment_include_false() { run_query("{ a, ... @include(if: false) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn anonymous_inline_fragment_skip_false() { run_query("{ a, ... @skip(if: false) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn anonymous_inline_fragment_skip_true() { run_query("{ a, ... @skip(if: true) { b } }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn scalar_include_true_skip_true() { run_query("{ a, b @include(if: true) @skip(if: true) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn scalar_include_true_skip_false() { run_query("{ a, b @include(if: true) @skip(if: false) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), Some(&graphql_value!("b"))); }) .await; } #[tokio::test] async fn scalar_include_false_skip_true() { run_query("{ a, b @include(if: false) @skip(if: true) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } #[tokio::test] async fn scalar_include_false_skip_false() { run_query("{ a, b @include(if: false) @skip(if: false) }", |result| { assert_eq!(result.get_field_value("a"), Some(&graphql_value!("a"))); assert_eq!(result.get_field_value("b"), None); }) .await; } juniper-0.16.2/src/executor_tests/enums.rs000064400000000000000000000100271046102023000167320ustar 00000000000000use crate::{ executor::Variables, graphql_value, graphql_vars, parser::SourcePosition, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, validation::RuleError, value::{DefaultScalarValue, Object}, GraphQLEnum, GraphQLError::ValidationError, }; #[derive(GraphQLEnum, Debug)] enum Color { Red, Green, Blue, } struct TestType; #[crate::graphql_object] impl TestType { fn to_string(color: Color) -> String { format!("Color::{color:?}") } fn a_color() -> Color { Color::Red } } async fn run_variable_query(query: &str, vars: Variables, f: F) where F: Fn(&Object), { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(query, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let obj = result.as_object_value().expect("Result is not an object"); f(obj); } async fn run_query(query: &str, f: F) where F: Fn(&Object), { run_variable_query(query, Variables::new(), f).await; } #[tokio::test] async fn accepts_enum_literal() { run_query("{ toString(color: RED) }", |result| { assert_eq!( result.get_field_value("toString"), Some(&graphql_value!("Color::Red")), ); }) .await; } #[tokio::test] async fn serializes_as_output() { run_query("{ aColor }", |result| { assert_eq!( result.get_field_value("aColor"), Some(&graphql_value!("RED")), ); }) .await; } #[tokio::test] async fn does_not_accept_string_literals() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"{ toString(color: "RED") }"#; let vars = graphql_vars! {}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Invalid value for argument "color", reason: Invalid value ""RED"" for enum "Color""#, &[SourcePosition::new(18, 0, 18)], )]) ); } #[tokio::test] async fn accepts_strings_in_variables() { run_variable_query( "query q($color: Color!) { toString(color: $color) }", graphql_vars! {"color": "RED"}, |result| { assert_eq!( result.get_field_value("toString"), Some(&graphql_value!("Color::Red")), ); }, ) .await; } #[tokio::test] async fn does_not_accept_incorrect_enum_name_in_variables() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($color: Color!) { toString(color: $color) }"#; let vars = graphql_vars! {"color": "BLURPLE"}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$color" got invalid value. Invalid value for enum "Color"."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_accept_incorrect_type_in_variables() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($color: Color!) { toString(color: $color) }"#; let vars = graphql_vars! {"color": 123}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$color" got invalid value. Expected "Color", found not a string or enum."#, &[SourcePosition::new(8, 0, 8)], )]), ); } juniper-0.16.2/src/executor_tests/executor.rs000064400000000000000000000727261046102023000174570ustar 00000000000000mod field_execution { use crate::{ graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; struct DataType; struct DeepDataType; #[graphql_object] impl DataType { fn a() -> &'static str { "Apple" } fn b() -> &'static str { "Banana" } fn c() -> &'static str { "Cookie" } fn d() -> &'static str { "Donut" } fn e() -> &'static str { "Egg" } fn f() -> &'static str { "Fish" } fn pic(size: Option) -> String { format!("Pic of size: {}", size.unwrap_or(50)) } fn deep() -> DeepDataType { DeepDataType } } #[graphql_object] impl DeepDataType { fn a() -> &'static str { "Already Been Done" } fn b() -> &'static str { "Boring" } fn c() -> Vec> { vec![Some("Contrived"), None, Some("Confusing")] } fn deeper() -> Vec> { vec![Some(DataType), None, Some(DataType)] } } #[tokio::test] async fn test() { let schema = RootNode::new( DataType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r" query Example($size: Int) { a, b, x: c ...c f ...on DataType { pic(size: $size) } deep { a b c deeper { a b } } } fragment c on DataType { d e } "; let vars = graphql_vars! {"size": 100}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "a": "Apple", "b": "Banana", "x": "Cookie", "d": "Donut", "e": "Egg", "f": "Fish", "pic": "Pic of size: 100", "deep": { "a": "Already Been Done", "b": "Boring", "c": ["Contrived", null, "Confusing"], "deeper": [ { "a": "Apple", "b": "Banana", }, null, { "a": "Apple", "b": "Banana", }, ], }, }), ); } } mod merge_parallel_fragments { use crate::{ graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; struct Type; #[graphql_object] impl Type { fn a() -> &'static str { "Apple" } fn b() -> &'static str { "Banana" } fn c() -> &'static str { "Cherry" } fn deep() -> Type { Type } } #[tokio::test] async fn test() { let schema = RootNode::new( Type, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r" { a, ...FragOne, ...FragTwo } fragment FragOne on Type { b deep { b, deeper: deep { b } } } fragment FragTwo on Type { c deep { c, deeper: deep { c } } } "; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "a": "Apple", "b": "Banana", "deep": { "b": "Banana", "deeper": { "b": "Banana", "c": "Cherry", }, "c": "Cherry", }, "c": "Cherry", }), ); } } mod merge_parallel_inline_fragments { use crate::{ graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; struct Type; struct Other; #[graphql_object] impl Type { fn a() -> &'static str { "Apple" } fn b() -> &'static str { "Banana" } fn c() -> &'static str { "Cherry" } fn deep() -> Type { Type } fn other() -> Vec { vec![Other, Other] } } #[graphql_object] impl Other { fn a() -> &'static str { "Apple" } fn b() -> &'static str { "Banana" } fn c() -> &'static str { "Cherry" } fn deep() -> Type { Type } fn other() -> Vec { vec![Other, Other] } } #[tokio::test] async fn test() { let schema = RootNode::new( Type, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r" { a, ...FragOne } fragment FragOne on Type { b deep: deep { b deeper: other { deepest: deep { b } } ... on Type { c deeper: other { deepest: deep { c } } } } c } "; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "a": "Apple", "b": "Banana", "deep": { "b": "Banana", "deeper": [{ "deepest": { "b": "Banana", "c": "Cherry", }, }, { "deepest": { "b": "Banana", "c": "Cherry", }, }], "c": "Cherry", }, "c": "Cherry", }), ); } } mod threads_context_correctly { use crate::{ executor::Context, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; struct Schema; struct TestContext { value: String, } impl Context for TestContext {} #[graphql_object(context = TestContext)] impl Schema { fn a(context: &TestContext) -> String { context.value.clone() } } #[tokio::test] async fn test() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ a }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute( doc, None, &schema, &vars, &TestContext { value: "Context value".into(), }, ) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"a": "Context value"})); } } mod dynamic_context_switching { use indexmap::IndexMap; use crate::{ executor::{Context, ExecutionError, FieldError, FieldResult}, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, Executor, ScalarValue, }; struct Schema; struct InnerContext { value: String, } struct OuterContext { items: IndexMap, } impl Context for OuterContext {} impl Context for InnerContext {} struct ItemRef; #[graphql_object(context = OuterContext)] impl Schema { fn item_opt<'e, S: ScalarValue>( executor: &'e Executor<'_, '_, OuterContext, S>, _context: &OuterContext, key: i32, ) -> Option<(&'e InnerContext, ItemRef)> { executor.context().items.get(&key).map(|c| (c, ItemRef)) } fn item_res(context: &OuterContext, key: i32) -> FieldResult<(&InnerContext, ItemRef)> { let res = context .items .get(&key) .ok_or(format!("Could not find key {key}")) .map(|c| (c, ItemRef))?; Ok(res) } fn item_res_opt( context: &OuterContext, key: i32, ) -> FieldResult> { if key > 100 { Err(format!("Key too large: {key}"))?; } Ok(context.items.get(&key).map(|c| (c, ItemRef))) } fn item_always(context: &OuterContext, key: i32) -> (&InnerContext, ItemRef) { context.items.get(&key).map(|c| (c, ItemRef)).unwrap() } } #[graphql_object(context = InnerContext)] impl ItemRef { fn value(context: &InnerContext) -> String { context.value.clone() } } #[tokio::test] async fn test_opt() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ first: itemOpt(key: 0) { value }, missing: itemOpt(key: 2) { value } }"; let vars = graphql_vars! {}; let ctx = OuterContext { items: vec![ ( 0, InnerContext { value: "First value".into(), }, ), ( 1, InnerContext { value: "Second value".into(), }, ), ] .into_iter() .collect(), }; let (result, errs) = crate::execute(doc, None, &schema, &vars, &ctx) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "first": {"value": "First value"}, "missing": null, }), ); } #[tokio::test] async fn test_res_success() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ first: itemRes(key: 0) { value } }"; let vars = graphql_vars! {}; let ctx = OuterContext { items: vec![ ( 0, InnerContext { value: "First value".into(), }, ), ( 1, InnerContext { value: "Second value".into(), }, ), ] .into_iter() .collect(), }; let (result, errs) = crate::execute(doc, None, &schema, &vars, &ctx) .await .expect("Execution failed"); assert_eq!(errs, vec![]); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } #[tokio::test] async fn test_res_fail() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ missing: itemRes(key: 2) { value } }"; let vars = graphql_vars! {}; let ctx = OuterContext { items: vec![ ( 0, InnerContext { value: "First value".into(), }, ), ( 1, InnerContext { value: "Second value".into(), }, ), ] .into_iter() .collect(), }; let (result, errs) = crate::execute(doc, None, &schema, &vars, &ctx) .await .expect("Execution failed"); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(14, 1, 12), &["missing"], FieldError::new("Could not find key 2", graphql_value!(null)), )], ); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); } #[tokio::test] async fn test_res_opt() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ first: itemResOpt(key: 0) { value } missing: itemResOpt(key: 2) { value } tooLarge: itemResOpt(key: 200) { value } }"; let vars = graphql_vars! {}; let ctx = OuterContext { items: vec![ ( 0, InnerContext { value: "First value".into(), }, ), ( 1, InnerContext { value: "Second value".into(), }, ), ] .into_iter() .collect(), }; let (result, errs) = crate::execute(doc, None, &schema, &vars, &ctx) .await .expect("Execution failed"); assert_eq!( errs, [ExecutionError::new( SourcePosition::new(112, 3, 12), &["tooLarge"], FieldError::new("Key too large: 200", graphql_value!(null)), )], ); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "first": {"value": "First value"}, "missing": null, "tooLarge": null, }), ); } #[tokio::test] async fn test_always() { let schema = RootNode::new( Schema, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r"{ first: itemAlways(key: 0) { value } }"; let vars = graphql_vars! {}; let ctx = OuterContext { items: vec![ ( 0, InnerContext { value: "First value".into(), }, ), ( 1, InnerContext { value: "Second value".into(), }, ), ] .into_iter() .collect(), }; let (result, errs) = crate::execute(doc, None, &schema, &vars, &ctx) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } } mod propagates_errors_to_nullable_fields { use crate::{ executor::{ExecutionError, FieldError, FieldResult, IntoFieldError}, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{ScalarValue, Value}, }; struct Schema; struct Inner; enum CustomError { NotFound, } impl IntoFieldError for CustomError where S: ScalarValue, { fn into_field_error(self) -> FieldError { match self { CustomError::NotFound => { let v: Value = graphql_value!({ "type": "NOT_FOUND" }); FieldError::new("Not Found", v) } } } } #[graphql_object] impl Schema { fn inner() -> Inner { Inner } fn inners() -> Vec { (0..5).map(|_| Inner).collect() } fn nullable_inners() -> Vec> { (0..5).map(|_| Some(Inner)).collect() } } #[graphql_object] impl Inner { fn nullable_field() -> Option { Some(Inner) } fn non_nullable_field() -> Inner { Inner } fn nullable_error_field() -> FieldResult> { Err("Error for nullableErrorField")? } fn non_nullable_error_field() -> FieldResult<&'static str> { Err("Error for nonNullableErrorField")? } fn custom_error_field() -> Result<&'static str, CustomError> { Err(CustomError::NotFound) } } #[tokio::test] async fn nullable_first_level() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { nullableErrorField } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({"inner": {"nullableErrorField": null}}), ); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(10, 0, 10), &["inner", "nullableErrorField"], FieldError::new("Error for nullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn non_nullable_first_level() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { nonNullableErrorField } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(10, 0, 10), &["inner", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn custom_error_first_level() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { customErrorField } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(10, 0, 10), &["inner", "customErrorField"], FieldError::new("Not Found", graphql_value!({"type": "NOT_FOUND"})), )], ); } #[tokio::test] async fn nullable_nested_level() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { nullableField { nonNullableErrorField } } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!({"inner": {"nullableField": null}}),); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(26, 0, 26), &["inner", "nullableField", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn non_nullable_nested_level() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { nonNullableField { nonNullableErrorField } } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(29, 0, 29), &["inner", "nonNullableField", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn nullable_innermost() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inner { nonNullableField { nullableErrorField } } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({"inner": {"nonNullableField": {"nullableErrorField": null}}}), ); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(29, 0, 29), &["inner", "nonNullableField", "nullableErrorField"], FieldError::new("Error for nullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn non_null_list() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ inners { nonNullableErrorField } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!(result, graphql_value!(null)); assert_eq!( errs, vec![ExecutionError::new( SourcePosition::new(11, 0, 11), &["inners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), )], ); } #[tokio::test] async fn non_null_list_of_nullable() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ nullableInners { nonNullableErrorField } }"; let vars = graphql_vars! {}; let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({"nullableInners": [null, null, null, null, null]}), ); assert_eq!( errs, vec![ ExecutionError::new( SourcePosition::new(19, 0, 19), &["nullableInners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), ), ExecutionError::new( SourcePosition::new(19, 0, 19), &["nullableInners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), ), ExecutionError::new( SourcePosition::new(19, 0, 19), &["nullableInners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), ), ExecutionError::new( SourcePosition::new(19, 0, 19), &["nullableInners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), ), ExecutionError::new( SourcePosition::new(19, 0, 19), &["nullableInners", "nonNullableErrorField"], FieldError::new("Error for nonNullableErrorField", graphql_value!(null)), ), ], ); } } mod named_operations { use crate::{ graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, GraphQLError, }; struct Schema; #[graphql_object] impl Schema { fn a(p: Option) -> &'static str { drop(p); "b" } } #[tokio::test] async fn uses_inline_operation_if_no_name_provided() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"{ a }"; let vars = graphql_vars! {}; let (res, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); assert_eq!(res, graphql_value!({"a": "b"})); } #[tokio::test] async fn uses_only_named_operation() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"query Example { a }"; let vars = graphql_vars! {}; let (res, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); assert_eq!(res, graphql_value!({"a": "b"})); } #[tokio::test] async fn uses_named_operation_if_name_provided() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"query Example($p: String!) { first: a(p: $p) } query OtherExample { second: a }"; let vars = graphql_vars! {}; let (res, errs) = crate::execute(doc, Some("OtherExample"), &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); assert_eq!(res, graphql_value!({"second": "b"})); } #[tokio::test] async fn error_if_multiple_operations_provided_but_no_name() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"query Example { first: a } query OtherExample { second: a }"; let vars = graphql_vars! {}; let err = crate::execute(doc, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!(err, GraphQLError::MultipleOperationsProvided); } #[tokio::test] async fn error_if_unknown_operation_name_provided() { let schema = RootNode::new( Schema, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r"query Example { first: a } query OtherExample { second: a }"; let vars = graphql_vars! {}; let err = crate::execute(doc, Some("UnknownExample"), &schema, &vars, &()) .await .unwrap_err(); assert_eq!(err, GraphQLError::UnknownOperationName); } } juniper-0.16.2/src/executor_tests/interfaces_unions.rs000064400000000000000000000114561046102023000213300ustar 00000000000000mod interface { use crate::{ graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, GraphQLObject, }; #[graphql_interface(for = [Cat, Dog])] trait Pet { fn name(&self) -> &str; } #[derive(GraphQLObject)] #[graphql(impl = PetValue)] struct Dog { name: String, woofs: bool, } #[derive(GraphQLObject)] #[graphql(impl = PetValue)] struct Cat { name: String, meows: bool, } struct Schema { pets: Vec, } #[graphql_object] impl Schema { fn pets(&self) -> &Vec { &self.pets } } #[tokio::test] async fn test() { let schema = RootNode::new( Schema { pets: vec![ Dog { name: "Odie".into(), woofs: true, } .into(), Cat { name: "Garfield".into(), meows: false, } .into(), ], }, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r" { pets { name ... on Dog { woofs } ... on Cat { meows } } }"; let vars = vec![].into_iter().collect(); let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "pets": [{ "name": "Odie", "woofs": true, }, { "name": "Garfield", "meows": false, }], }), ); } } mod union { use crate::{ graphql_object, graphql_union, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; #[graphql_union] trait Pet { fn as_dog(&self) -> Option<&Dog> { None } fn as_cat(&self) -> Option<&Cat> { None } } struct Dog { name: String, woofs: bool, } impl Pet for Dog { fn as_dog(&self) -> Option<&Dog> { Some(self) } } #[graphql_object] impl Dog { fn name(&self) -> &str { &self.name } fn woofs(&self) -> bool { self.woofs } } struct Cat { name: String, meows: bool, } impl Pet for Cat { fn as_cat(&self) -> Option<&Cat> { Some(self) } } #[graphql_object] impl Cat { fn name(&self) -> &str { &self.name } fn meows(&self) -> bool { self.meows } } struct Schema { pets: Vec>, } #[graphql_object] impl Schema { fn pets(&self) -> Vec<&(dyn Pet + Send + Sync)> { self.pets.iter().map(|p| p.as_ref()).collect() } } #[tokio::test] async fn test_unions() { let schema = RootNode::new( Schema { pets: vec![ Box::new(Dog { name: "Odie".into(), woofs: true, }), Box::new(Cat { name: "Garfield".into(), meows: false, }), ], }, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let doc = r" { pets { __typename ... on Dog { name woofs } ... on Cat { name meows } } }"; let vars = vec![].into_iter().collect(); let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "pets": [{ "__typename": "Dog", "name": "Odie", "woofs": true, }, { "__typename": "Cat", "name": "Garfield", "meows": false, }], }), ); } } juniper-0.16.2/src/executor_tests/introspection/enums.rs000064400000000000000000000236441046102023000216430ustar 00000000000000use crate::{ graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object, Value}, GraphQLEnum, }; /* Syntax to validate: * Default name vs. custom name * Trailing comma vs. no trailing comma * Description vs. no description on the enum * Description vs. no description on the enum values themselves * Deprecation on enum fields */ #[derive(GraphQLEnum)] enum DefaultName { Foo, Bar, } #[derive(GraphQLEnum)] #[graphql(name = "ANamedEnum")] enum Named { Foo, Bar, } #[derive(GraphQLEnum)] enum NoTrailingComma { Foo, Bar, } #[derive(GraphQLEnum)] #[graphql(description = "A description of the enum itself")] enum EnumDescription { Foo, Bar, } #[derive(GraphQLEnum)] enum EnumValueDescription { #[graphql(description = "The FOO value")] Foo, #[graphql(description = "The BAR value")] Bar, } #[derive(GraphQLEnum)] enum EnumDeprecation { #[graphql(deprecated = "Please don't use FOO any more")] Foo, #[graphql( description = "The BAR value", deprecated = "Please don't use BAR any more" )] Bar, } struct Root; #[crate::graphql_object] impl Root { fn default_name() -> DefaultName { DefaultName::Foo } fn named() -> Named { Named::Foo } fn no_trailing_comma() -> NoTrailingComma { NoTrailingComma::Foo } fn enum_description() -> EnumDescription { EnumDescription::Foo } fn enum_value_description() -> EnumValueDescription { EnumValueDescription::Foo } fn enum_deprecation() -> EnumDeprecation { EnumDeprecation::Foo } } async fn run_type_info_query(doc: &str, f: F) where F: Fn((&Object, &Vec>)), { let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let values = type_info .get_field_value("enumValues") .expect("enumValues field missing") .as_list_value() .expect("enumValues not a list"); f((type_info, values)); } #[tokio::test] async fn default_name_introspection() { let doc = r#" { __type(name: "DefaultName") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("DefaultName")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": null, "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": null, "isDeprecated": false, "deprecationReason": null, }))); }) .await; } #[tokio::test] async fn named_introspection() { let doc = r#" { __type(name: "ANamedEnum") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("ANamedEnum")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": null, "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": null, "isDeprecated": false, "deprecationReason": null, }))); }) .await; } #[tokio::test] async fn no_trailing_comma_introspection() { let doc = r#" { __type(name: "NoTrailingComma") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("NoTrailingComma")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": null, "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": null, "isDeprecated": false, "deprecationReason": null, }))); }) .await; } #[tokio::test] async fn enum_description_introspection() { let doc = r#" { __type(name: "EnumDescription") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("EnumDescription")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!("A description of the enum itself")), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": null, "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": null, "isDeprecated": false, "deprecationReason": null, }))); }) .await; } #[tokio::test] async fn enum_value_description_introspection() { let doc = r#" { __type(name: "EnumValueDescription") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("EnumValueDescription")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": "The FOO value", "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": "The BAR value", "isDeprecated": false, "deprecationReason": null, }))); }) .await; } #[tokio::test] async fn enum_deprecation_introspection() { let doc = r#" { __type(name: "EnumDeprecation") { name description enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("EnumDeprecation")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "FOO", "description": null, "isDeprecated": true, "deprecationReason": "Please don't use FOO any more", }))); assert!(values.contains(&graphql_value!({ "name": "BAR", "description": "The BAR value", "isDeprecated": true, "deprecationReason": "Please don't use BAR any more", }))); }) .await; } #[tokio::test] async fn enum_deprecation_no_values_introspection() { let doc = r#" { __type(name: "EnumDeprecation") { name description enumValues { name description isDeprecated deprecationReason } } } "#; run_type_info_query(doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("EnumDeprecation")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(values.len(), 0); }) .await; } juniper-0.16.2/src/executor_tests/introspection/input_object.rs000064400000000000000000000273661046102023000232060ustar 00000000000000#![deny(unused_variables)] use crate::{ ast::{FromInputValue, InputValue}, graphql_input_value, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object, Value}, GraphQLInputObject, }; struct Root; #[derive(GraphQLInputObject, Debug)] struct DefaultName { field_one: String, field_two: String, } #[derive(GraphQLInputObject, Debug)] struct NoTrailingComma { field_one: String, field_two: String, } #[derive(GraphQLInputObject, Debug)] struct Derive { field_one: String, } #[derive(GraphQLInputObject, Debug)] #[graphql(name = "ANamedInputObject")] struct Named { field_one: String, } #[derive(GraphQLInputObject, Debug)] #[graphql(description = "Description for the input object")] struct Description { field_one: String, } #[derive(GraphQLInputObject, Debug)] pub struct Public { field_one: String, } #[derive(GraphQLInputObject, Debug)] #[graphql(description = "Description for the input object")] pub struct PublicWithDescription { field_one: String, } #[derive(GraphQLInputObject, Debug)] #[graphql( name = "APublicNamedInputObjectWithDescription", description = "Description for the input object" )] pub struct NamedPublicWithDescription { field_one: String, } #[derive(GraphQLInputObject, Debug)] #[graphql(name = "APublicNamedInputObject")] pub struct NamedPublic { field_one: String, } #[derive(GraphQLInputObject, Debug)] struct FieldDescription { #[graphql(description = "The first field")] field_one: String, #[graphql(description = "The second field")] field_two: String, } #[derive(GraphQLInputObject, Debug)] struct FieldWithDefaults { #[graphql(default = 123)] field_one: i32, #[graphql(default = 456, description = "The second field")] field_two: i32, } #[graphql_object] impl Root { fn test_field( a1: DefaultName, a2: NoTrailingComma, a3: Derive, a4: Named, a5: Description, a6: FieldDescription, a7: Public, a8: PublicWithDescription, a9: NamedPublicWithDescription, a10: NamedPublic, a11: FieldWithDefaults, ) -> i32 { let _ = a1; let _ = a2; let _ = a3; let _ = a4; let _ = a5; let _ = a6; let _ = a7; let _ = a8; let _ = a9; let _ = a10; let _ = a11; 0 } } async fn run_type_info_query(doc: &str, f: F) where F: Fn(&Object, &Vec>), { let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info .get_field_value("inputFields") .expect("inputFields field missing") .as_list_value() .expect("inputFields not a list"); f(type_info, fields); } #[tokio::test] async fn default_name_introspection() { let doc = r#"{ __type(name: "DefaultName") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("DefaultName")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(fields.len(), 2); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); assert!(fields.contains(&graphql_value!({ "name": "fieldTwo", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[test] fn default_name_input_value() { let iv: InputValue = graphql_input_value!({ "fieldOne": "number one", "fieldTwo": "number two", }); let dv = DefaultName::from_input_value(&iv); assert!(dv.is_ok(), "error: {}", dv.unwrap_err().message()); let dv = dv.unwrap(); assert_eq!(dv.field_one, "number one"); assert_eq!(dv.field_two, "number two"); } #[tokio::test] async fn no_trailing_comma_introspection() { let doc = r#"{ __type(name: "NoTrailingComma") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("NoTrailingComma")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(fields.len(), 2); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); assert!(fields.contains(&graphql_value!({ "name": "fieldTwo", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[tokio::test] async fn derive_introspection() { let doc = r#"{ __type(name: "Derive") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("Derive")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(fields.len(), 1); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[test] fn derive_derived() { assert_eq!( format!( "{:?}", Derive { field_one: "test".into(), }, ), "Derive { field_one: \"test\" }" ); } #[tokio::test] async fn named_introspection() { let doc = r#"{ __type(name: "ANamedInputObject") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("ANamedInputObject")) ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)) ); assert_eq!(fields.len(), 1); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[tokio::test] async fn description_introspection() { let doc = r#"{ __type(name: "Description") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("Description")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!("Description for the input object")), ); assert_eq!(fields.len(), 1); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": null, "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[tokio::test] async fn field_description_introspection() { let doc = r#"{ __type(name: "FieldDescription") { name description inputFields { name description type { ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("FieldDescription")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!(fields.len(), 2); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "description": "The first field", "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); assert!(fields.contains(&graphql_value!({ "name": "fieldTwo", "description": "The second field", "type": { "ofType": {"name": "String"}, }, "defaultValue": null, }))); }) .await; } #[tokio::test] async fn field_with_defaults_introspection() { let doc = r#"{ __type(name: "FieldWithDefaults") { name inputFields { name type { name ofType { name } } defaultValue } } }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("FieldWithDefaults")), ); assert_eq!(fields.len(), 2); assert!(fields.contains(&graphql_value!({ "name": "fieldOne", "type": {"name": null, "ofType": {"name": "Int"}}, "defaultValue": "123", }))); assert!(fields.contains(&graphql_value!({ "name": "fieldTwo", "type": {"name": null, "ofType": {"name": "Int"}}, "defaultValue": "456", }))); }) .await; } juniper-0.16.2/src/executor_tests/introspection/mod.rs000064400000000000000000000316571046102023000212760ustar 00000000000000mod enums; mod input_object; // This asserts that the input objects defined public actually became public #[allow(unused_imports)] use self::input_object::{NamedPublic, NamedPublicWithDescription}; use crate::{ graphql_interface, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, GraphQLEnum, GraphQLScalar, }; #[derive(GraphQLEnum)] #[graphql(name = "SampleEnum")] enum Sample { One, Two, } #[derive(GraphQLScalar)] #[graphql(name = "SampleScalar", transparent)] struct Scalar(i32); /// A sample interface #[graphql_interface(name = "SampleInterface", for = Root)] trait Interface { /// A sample field in the interface fn sample_enum(&self) -> Sample; } struct Root; /// The root query object in the schema #[graphql_object(impl = InterfaceValue)] impl Root { fn sample_enum() -> Sample { Sample::One } /// A sample scalar field on the object fn sample_scalar( #[graphql(description = "The first number")] first: i32, #[graphql(description = "The second number", default = 123)] second: i32, ) -> Scalar { Scalar(first + second) } } #[tokio::test] async fn test_execution() { let doc = r#" { sampleEnum first: sampleScalar(first: 0) second: sampleScalar(first: 10 second: 20) } "#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); assert_eq!( result, graphql_value!({ "sampleEnum": "ONE", "first": 123, "second": 30, }), ); } #[tokio::test] async fn enum_introspection() { let doc = r#" { __type(name: "SampleEnum") { name kind description enumValues { name description isDeprecated deprecationReason } interfaces { name } possibleTypes { name } inputFields { name } ofType { name } } } "#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("SampleEnum")), ); assert_eq!( type_info.get_field_value("kind"), Some(&graphql_value!("ENUM")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("interfaces"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("possibleTypes"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("inputFields"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("ofType"), Some(&graphql_value!(null)) ); let values = type_info .get_field_value("enumValues") .expect("enumValues field missing") .as_list_value() .expect("enumValues not a list"); assert_eq!(values.len(), 2); assert!(values.contains(&graphql_value!({ "name": "ONE", "description": null, "isDeprecated": false, "deprecationReason": null, }))); assert!(values.contains(&graphql_value!({ "name": "TWO", "description": null, "isDeprecated": false, "deprecationReason": null, }))); } #[tokio::test] async fn interface_introspection() { let doc = r#" { __type(name: "SampleInterface") { name kind description possibleTypes { name } fields { name description args { name } type { name kind ofType { name kind } } isDeprecated deprecationReason } interfaces { name } enumValues { name } inputFields { name } ofType { name } } } "#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("SampleInterface")), ); assert_eq!( type_info.get_field_value("kind"), Some(&graphql_value!("INTERFACE")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!("A sample interface")), ); assert_eq!( type_info.get_field_value("interfaces"), Some(&graphql_value!([])), ); assert_eq!( type_info.get_field_value("enumValues"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("inputFields"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("ofType"), Some(&graphql_value!(null)) ); let possible_types = type_info .get_field_value("possibleTypes") .expect("possibleTypes field missing") .as_list_value() .expect("possibleTypes not a list"); assert_eq!(possible_types.len(), 1); assert!(possible_types.contains(&graphql_value!({"name": "Root"}))); let fields = type_info .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not an object value"); assert_eq!(fields.len(), 1); assert!(fields.contains(&graphql_value!({ "name": "sampleEnum", "description": "A sample field in the interface", "args": [], "type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "SampleEnum", "kind": "ENUM", }, }, "isDeprecated": false, "deprecationReason": null, }))); } #[tokio::test] async fn object_introspection() { let doc = r#" { __type(name: "Root") { name kind description fields { name description args { name description type { name kind ofType { name kind ofType { name } } } defaultValue } type { name kind ofType { name kind } } isDeprecated deprecationReason } possibleTypes { name } interfaces { name } enumValues { name } inputFields { name } ofType { name } } } "#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); assert_eq!( type_info.get_field_value("name"), Some(&graphql_value!("Root")), ); assert_eq!( type_info.get_field_value("kind"), Some(&graphql_value!("OBJECT")), ); assert_eq!( type_info.get_field_value("description"), Some(&graphql_value!("The root query object in the schema")), ); assert_eq!( type_info.get_field_value("interfaces"), Some(&graphql_value!([{"name": "SampleInterface"}])), ); assert_eq!( type_info.get_field_value("enumValues"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("inputFields"), Some(&graphql_value!(null)), ); assert_eq!( type_info.get_field_value("ofType"), Some(&graphql_value!(null)) ); assert_eq!( type_info.get_field_value("possibleTypes"), Some(&graphql_value!(null)), ); let fields = type_info .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not an object value"); assert_eq!(fields.len(), 2); println!("Fields: {fields:#?}"); assert!(fields.contains(&graphql_value!({ "name": "sampleEnum", "description": null, "args": [], "type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "SampleEnum", "kind": "ENUM", }, }, "isDeprecated": false, "deprecationReason": null, }))); assert!(fields.contains(&graphql_value!({ "name": "sampleScalar", "description": "A sample scalar field on the object", "args": [{ "name": "first", "description": "The first number", "type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "Int", "kind": "SCALAR", "ofType": null, }, }, "defaultValue": null, }, { "name": "second", "description": "The second number", "type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "Int", "kind": "SCALAR", "ofType": null, }, }, "defaultValue": "123", }], "type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "SampleScalar", "kind": "SCALAR", }, }, "isDeprecated": false, "deprecationReason": null, }))); } #[tokio::test] async fn scalar_introspection() { let doc = r#" { __type(name: "SampleScalar") { name kind description specifiedByUrl fields { name } interfaces { name } possibleTypes { name } enumValues { name } inputFields { name } ofType { name } } } "#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:#?}"); let type_info = result .as_object_value() .expect("Result is not an object") .get_field_value("__type") .expect("__type field missing"); assert_eq!( type_info, &graphql_value!({ "name": "SampleScalar", "kind": "SCALAR", "description": null, "specifiedByUrl": null, "fields": null, "interfaces": null, "possibleTypes": null, "enumValues": null, "inputFields": null, "ofType": null, }), ); } juniper-0.16.2/src/executor_tests/mod.rs000064400000000000000000000001651046102023000163640ustar 00000000000000mod directives; mod enums; mod executor; mod introspection; mod variables; mod interfaces_unions; mod async_await; juniper-0.16.2/src/executor_tests/variables.rs000064400000000000000000001010251046102023000175520ustar 00000000000000use crate::{ executor::Variables, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, validation::RuleError, value::{DefaultScalarValue, Object}, GraphQLError::ValidationError, GraphQLInputObject, GraphQLScalar, InputValue, ScalarValue, Value, }; #[derive(Debug, GraphQLScalar)] #[graphql(parse_token(String))] struct TestComplexScalar; impl TestComplexScalar { fn to_output(&self) -> Value { graphql_value!("SerializedValue") } fn from_input(v: &InputValue) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") .map(|_| Self) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {v}"#)) } } #[derive(GraphQLInputObject, Debug)] struct TestInputObject { a: Option, b: Option>>, c: String, d: Option, } #[derive(GraphQLInputObject, Debug)] struct TestNestedInputObject { na: TestInputObject, nb: String, } #[derive(GraphQLInputObject, Debug)] struct ExampleInputObject { a: Option, b: i32, } #[derive(GraphQLInputObject, Debug)] struct InputWithDefaults { #[graphql(default = 123)] a: i32, } struct TestType; #[graphql_object] impl TestType { fn field_with_object_input(input: Option) -> String { format!("{input:?}") } fn field_with_nullable_string_input(input: Option) -> String { format!("{input:?}") } fn field_with_non_nullable_string_input(input: String) -> String { format!("{input:?}") } fn field_with_default_argument_value( #[graphql(default = "Hello World")] input: String, ) -> String { format!("{input:?}") } fn nullable_field_with_default_argument_value( #[graphql(default = "Hello World".to_owned())] input: Option, ) -> String { format!("{input:?}") } fn field_with_nested_object_input(input: Option) -> String { format!("{input:?}") } fn list(input: Option>>) -> String { format!("{input:?}") } fn nn_list(input: Vec>) -> String { format!("{input:?}") } fn list_nn(input: Option>) -> String { format!("{input:?}") } fn nn_list_nn(input: Vec) -> String { format!("{input:?}") } fn example_input(arg: ExampleInputObject) -> String { format!("a: {:?}, b: {:?}", arg.a, arg.b) } fn input_with_defaults(arg: InputWithDefaults) -> String { format!("a: {:?}", arg.a) } fn integer_input(value: i32) -> String { format!("value: {value}") } fn float_input(value: f64) -> String { format!("value: {value}") } } async fn run_variable_query(query: &str, vars: Variables, f: F) where F: Fn(&Object), { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let (result, errs) = crate::execute(query, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); println!("Result: {result:?}"); let obj = result.as_object_value().expect("Result is not an object"); f(obj); } async fn run_query(query: &str, f: F) where F: Fn(&Object), { run_variable_query(query, graphql_vars! {}, f).await; } #[tokio::test] async fn inline_complex_input() { run_query( r#"{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# )) ); }, ).await; } #[tokio::test] async fn inline_parse_single_value_to_list() { run_query( r#"{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# )) ); }, ).await; } #[tokio::test] async fn inline_runs_from_input_value_on_scalar() { run_query( r#"{ fieldWithObjectInput(input: {c: "baz", d: "SerializedValue"}) }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"# )) ); }, ).await; } #[tokio::test] async fn variable_complex_input() { run_variable_query( r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#, graphql_vars! { "input": { "a": "foo", "b": ["bar"], "c": "baz", }, }, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# )) ); }, ).await; } #[tokio::test] async fn variable_parse_single_value_to_list() { run_variable_query( r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#, graphql_vars! { "input": { "a": "foo", "b": "bar", "c": "baz", }, }, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# )) ); }, ).await; } #[tokio::test] async fn variable_runs_from_input_value_on_scalar() { run_variable_query( r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#, graphql_vars! { "input": { "c": "baz", "d": "SerializedValue", }, }, |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), Some(&graphql_value!( r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"# )) ); }, ).await; } #[tokio::test] async fn variable_error_on_nested_non_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#; let vars = graphql_vars! { "input": { "a": "foo", "b": "bar", "c": null, }, }; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn variable_error_on_incorrect_type() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#; let vars = graphql_vars! {"input": "foo bar"}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. Expected "TestInputObject", found not an object."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn variable_error_on_omit_non_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#; let vars = graphql_vars! { "input": { "a": "foo", "b": "bar", }, }; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn variable_multiple_errors_with_nesting() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: TestNestedInputObject) { fieldWithNestedObjectInput(input: $input) }"#; let vars = graphql_vars! { "input": { "na": {"a": "foo"}, }, }; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![ RuleError::new( r#"Variable "$input" got invalid value. In field "na": In field "c": Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], ), RuleError::new( r#"Variable "$input" got invalid value. In field "nb": Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], ), ]), ); } #[tokio::test] async fn variable_error_on_additional_field() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#; let vars = graphql_vars! { "input": { "a": "foo", "b": "bar", "c": "baz", "extra": "dog", }, }; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In field "extra": Unknown field."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn allow_nullable_inputs_to_be_omitted() { run_query( r#"{ fieldWithNullableStringInput }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"None"#)) ); }, ) .await; } #[tokio::test] async fn allow_nullable_inputs_to_be_omitted_in_variable() { run_query( r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"None"#)) ); }, ) .await; } #[tokio::test] async fn allow_nullable_inputs_to_be_explicitly_null() { run_query( r#"{ fieldWithNullableStringInput(input: null) }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"None"#)) ); }, ) .await; } #[tokio::test] async fn allow_nullable_inputs_to_be_set_to_null_in_variable() { run_variable_query( r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#, graphql_vars! {"value": null}, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"None"#)) ); }, ) .await; } #[tokio::test] async fn allow_nullable_inputs_to_be_set_to_value_in_variable() { run_variable_query( r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#, graphql_vars! {"value": "a"}, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"Some("a")"#)) ); }, ) .await; } #[tokio::test] async fn allow_nullable_inputs_to_be_set_to_value_directly() { run_query( r#"{ fieldWithNullableStringInput(input: "a") }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNullableStringInput"), Some(&graphql_value!(r#"Some("a")"#)) ); }, ) .await; } #[tokio::test] async fn does_not_allow_non_nullable_input_to_be_omitted_in_variable() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#; let vars = graphql_vars! {}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$value" of required type "String!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_allow_non_nullable_input_to_be_set_to_null_in_variable() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#; let vars = graphql_vars! {"value": null}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$value" of required type "String!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn allow_non_nullable_inputs_to_be_set_to_value_in_variable() { run_variable_query( r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#, graphql_vars! {"value": "a"}, |result| { assert_eq!( result.get_field_value("fieldWithNonNullableStringInput"), Some(&graphql_value!(r#""a""#)), ); }, ) .await; } #[tokio::test] async fn allow_non_nullable_inputs_to_be_set_to_value_directly() { run_query( r#"{ fieldWithNonNullableStringInput(input: "a") }"#, |result: &Object| { assert_eq!( result.get_field_value("fieldWithNonNullableStringInput"), Some(&graphql_value!(r#""a""#)), ); }, ) .await; } #[tokio::test] async fn allow_lists_to_be_null() { run_variable_query( r#"query q($input: [String]) { list(input: $input) }"#, graphql_vars! {"input": null}, |result: &Object| { assert_eq!( result.get_field_value("list"), Some(&graphql_value!(r#"None"#)), ); }, ) .await; } #[tokio::test] async fn allow_lists_to_contain_values() { run_variable_query( r#"query q($input: [String]) { list(input: $input) }"#, graphql_vars! {"input": ["A"]}, |result| { assert_eq!( result.get_field_value("list"), Some(&graphql_value!(r#"Some([Some("A")])"#)), ); }, ) .await; } #[tokio::test] async fn allow_lists_to_contain_null() { run_variable_query( r#"query q($input: [String]) { list(input: $input) }"#, graphql_vars! {"input": ["A", null, "B"]}, |result| { assert_eq!( result.get_field_value("list"), Some(&graphql_value!(r#"Some([Some("A"), None, Some("B")])"#)), ); }, ) .await; } #[tokio::test] async fn does_not_allow_non_null_lists_to_be_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: [String]!) { nnList(input: $input) }"#; let vars = graphql_vars! {"input": null}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" of required type "[String]!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn allow_non_null_lists_to_contain_values() { run_variable_query( r#"query q($input: [String]!) { nnList(input: $input) }"#, graphql_vars! {"input": ["A"]}, |result| { assert_eq!( result.get_field_value("nnList"), Some(&graphql_value!(r#"[Some("A")]"#)), ); }, ) .await; } #[tokio::test] async fn allow_non_null_lists_to_contain_null() { run_variable_query( r#"query q($input: [String]!) { nnList(input: $input) }"#, graphql_vars! {"input": ["A", null, "B"]}, |result| { assert_eq!( result.get_field_value("nnList"), Some(&graphql_value!(r#"[Some("A"), None, Some("B")]"#)), ); }, ) .await; } #[tokio::test] async fn allow_lists_of_non_null_to_be_null() { run_variable_query( r#"query q($input: [String!]) { listNn(input: $input) }"#, graphql_vars! {"input": null}, |result| { assert_eq!( result.get_field_value("listNn"), Some(&graphql_value!(r#"None"#)), ); }, ) .await; } #[tokio::test] async fn allow_lists_of_non_null_to_contain_values() { run_variable_query( r#"query q($input: [String!]) { listNn(input: $input) }"#, graphql_vars! {"input": ["A"]}, |result| { assert_eq!( result.get_field_value("listNn"), Some(&graphql_value!(r#"Some(["A"])"#)), ); }, ) .await; } #[tokio::test] async fn does_not_allow_lists_of_non_null_to_contain_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: [String!]) { listNn(input: $input) }"#; let vars = graphql_vars! {"input": ["A", null, "B"]}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_allow_non_null_lists_of_non_null_to_contain_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#; let vars = graphql_vars! {"input": ["A", null, "B"]}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_allow_non_null_lists_of_non_null_to_be_null() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#; let vars = graphql_vars! {"input": null}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$input" of required type "[String!]!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn allow_non_null_lists_of_non_null_to_contain_values() { run_variable_query( r#"query q($input: [String!]!) { nnListNn(input: $input) }"#, graphql_vars! {"input": ["A"]}, |result| { assert_eq!( result.get_field_value("nnListNn"), Some(&graphql_value!(r#"["A"]"#)), ); }, ) .await; } #[tokio::test] async fn default_argument_when_not_provided() { run_query(r#"{ fieldWithDefaultArgumentValue }"#, |result| { assert_eq!( result.get_field_value("fieldWithDefaultArgumentValue"), Some(&graphql_value!(r#""Hello World""#)), ); }) .await; } #[tokio::test] async fn provided_variable_overwrites_default_value() { run_variable_query( r#"query q($input: String!) { fieldWithDefaultArgumentValue(input: $input) }"#, graphql_vars! {"input": "Overwritten"}, |result| { assert_eq!( result.get_field_value("fieldWithDefaultArgumentValue"), Some(&graphql_value!(r#""Overwritten""#)), ); }, ) .await; } #[tokio::test] async fn default_argument_when_nullable_variable_not_provided() { run_query( r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#, |result| { assert_eq!( result.get_field_value("nullableFieldWithDefaultArgumentValue"), Some(&graphql_value!(r#"Some("Hello World")"#)), ); }, ) .await; } #[tokio::test] async fn null_when_nullable_variable_of_argument_with_default_value_set_to_null() { run_variable_query( r#"query q($input: String) { nullableFieldWithDefaultArgumentValue(input: $input) }"#, graphql_vars! {"input": null}, |result| { assert_eq!( result.get_field_value("nullableFieldWithDefaultArgumentValue"), Some(&graphql_value!(r#"None"#)), ); }, ) .await; } #[tokio::test] async fn nullable_input_object_arguments_successful_without_variables() { run_query(r#"{ exampleInput(arg: {a: "abc", b: 123}) }"#, |result| { assert_eq!( result.get_field_value("exampleInput"), Some(&graphql_value!(r#"a: Some("abc"), b: 123"#)), ); }) .await; run_query(r#"{ exampleInput(arg: {a: null, b: 1}) }"#, |result| { assert_eq!( result.get_field_value("exampleInput"), Some(&graphql_value!(r#"a: None, b: 1"#)), ); }) .await; } #[tokio::test] async fn nullable_input_object_arguments_successful_with_variables() { run_variable_query( r#"query q($var: Int!) { exampleInput(arg: {b: $var}) }"#, graphql_vars! {"var": 123}, |result| { assert_eq!( result.get_field_value("exampleInput"), Some(&graphql_value!(r#"a: None, b: 123"#)), ); }, ) .await; run_variable_query( r#"query q($var: String) { exampleInput(arg: {a: $var, b: 1}) }"#, graphql_vars! {"var": null}, |result| { assert_eq!( result.get_field_value("exampleInput"), Some(&graphql_value!(r#"a: None, b: 1"#)), ); }, ) .await; run_variable_query( r#"query q($var: String) { exampleInput(arg: {a: $var, b: 1}) }"#, graphql_vars! {}, |result| { assert_eq!( result.get_field_value("exampleInput"), Some(&graphql_value!(r#"a: None, b: 1"#)), ); }, ) .await; } #[tokio::test] async fn does_not_allow_missing_required_field() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"{ exampleInput(arg: {a: "abc"}) }"#; let vars = graphql_vars! {}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( "Invalid value for argument \"arg\", \ reason: \"ExampleInputObject\" is missing fields: \"b\"", &[SourcePosition::new(20, 0, 20)], )]), ); } #[tokio::test] async fn does_not_allow_null_in_required_field() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"{ exampleInput(arg: {a: "abc", b: null}) }"#; let vars = graphql_vars! {}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( "Invalid value for argument \"arg\", \ reason: Error on \"ExampleInputObject\" field \"b\": \ \"null\" specified for not nullable type \"Int!\"", &[SourcePosition::new(20, 0, 20)], )]), ); } #[tokio::test] async fn does_not_allow_missing_variable_for_required_field() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($var: Int!) { exampleInput(arg: {b: $var}) }"#; let vars = graphql_vars! {}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$var" of required type "Int!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_allow_null_variable_for_required_field() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($var: Int!) { exampleInput(arg: {b: $var}) }"#; let vars = graphql_vars! {"var": null}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( r#"Variable "$var" of required type "Int!" was not provided."#, &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn input_object_with_default_values() { run_query(r#"{ inputWithDefaults(arg: {a: 1}) }"#, |result| { assert_eq!( result.get_field_value("inputWithDefaults"), Some(&graphql_value!(r#"a: 1"#)), ); }) .await; run_variable_query( r#"query q($var: Int!) { inputWithDefaults(arg: {a: $var}) }"#, graphql_vars! {"var": 1}, |result| { assert_eq!( result.get_field_value("inputWithDefaults"), Some(&graphql_value!(r#"a: 1"#)), ); }, ) .await; run_variable_query( r#"query q($var: Int = 1) { inputWithDefaults(arg: {a: $var}) }"#, graphql_vars! {}, |result| { assert_eq!( result.get_field_value("inputWithDefaults"), Some(&graphql_value!(r#"a: 1"#)), ); }, ) .await; run_variable_query( r#"query q($var: Int = 1) { inputWithDefaults(arg: {a: $var}) }"#, graphql_vars! {"var": 2}, |result| { assert_eq!( result.get_field_value("inputWithDefaults"), Some(&graphql_value!(r#"a: 2"#)), ); }, ) .await; } mod integers { use super::*; #[tokio::test] async fn positive_and_negative_should_work() { run_variable_query( r#"query q($var: Int!) { integerInput(value: $var) }"#, graphql_vars! {"var": 1}, |result| { assert_eq!( result.get_field_value("integerInput"), Some(&graphql_value!(r#"value: 1"#)), ); }, ) .await; run_variable_query( r#"query q($var: Int!) { integerInput(value: $var) }"#, graphql_vars! {"var": -1}, |result| { assert_eq!( result.get_field_value("integerInput"), Some(&graphql_value!(r#"value: -1"#)), ); }, ) .await; } #[tokio::test] async fn does_not_coerce_from_float() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($var: Int!) { integerInput(value: $var) }"#; let vars = graphql_vars! {"var": 10.0}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( "Variable \"$var\" got invalid value. Expected input scalar `Int`. \ Got: `10`. Details: Expected `Int`, found: 10.", &[SourcePosition::new(8, 0, 8)], )]), ); } #[tokio::test] async fn does_not_coerce_from_string() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($var: Int!) { integerInput(value: $var) }"#; let vars = graphql_vars! {"var": "10"}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( "Variable \"$var\" got invalid value. \ Expected input scalar `Int`. Got: `\"10\"`. \ Details: Expected `Int`, found: \"10\".", &[SourcePosition::new(8, 0, 8)], )]), ); } } mod floats { use super::*; #[tokio::test] async fn float_values_should_work() { run_variable_query( r#"query q($var: Float!) { floatInput(value: $var) }"#, graphql_vars! {"var": 10.0}, |result| { assert_eq!( result.get_field_value("floatInput"), Some(&graphql_value!(r#"value: 10"#)), ); }, ) .await; } #[tokio::test] async fn coercion_from_integers_should_work() { run_variable_query( r#"query q($var: Float!) { floatInput(value: $var) }"#, graphql_vars! {"var": -1}, |result| { assert_eq!( result.get_field_value("floatInput"), Some(&graphql_value!(r#"value: -1"#)), ); }, ) .await; } #[tokio::test] async fn does_not_coerce_from_string() { let schema = RootNode::new( TestType, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let query = r#"query q($var: Float!) { floatInput(value: $var) }"#; let vars = graphql_vars! {"var": "10"}; let error = crate::execute(query, None, &schema, &vars, &()) .await .unwrap_err(); assert_eq!( error, ValidationError(vec![RuleError::new( "Variable \"$var\" got invalid value. \ Expected input scalar `Float`. Got: `\"10\"`. \ Details: Expected `Float`, found: \"10\".", &[SourcePosition::new(8, 0, 8)], )]), ); } } juniper-0.16.2/src/http/graphiql.html000064400000000000000000000045511046102023000156360ustar 00000000000000 GraphiQL
Loading...
juniper-0.16.2/src/http/graphiql.js000064400000000000000000000007661046102023000153120ustar 00000000000000function normalizeSubscriptionEndpoint(endpoint, subscriptionEndpoint) { if (subscriptionEndpoint) { if (subscriptionEndpoint.startsWith('/')) { const secure = endpoint.includes('https') || location.href.includes('https') ? 's' : '' return `ws${secure}://${location.host}${subscriptionEndpoint}` } else { return subscriptionEndpoint.replace(/^http/, 'ws') } } return null } juniper-0.16.2/src/http/graphiql.rs000064400000000000000000000020261046102023000153110ustar 00000000000000//! Utility module to generate a GraphiQL interface /// Generate the HTML source to show a GraphiQL interface /// /// The subscriptions endpoint URL can optionally be provided. For example: /// /// ``` /// # use juniper::http::graphiql::graphiql_source; /// let graphiql = graphiql_source("/graphql", Some("/subscriptions")); /// ``` pub fn graphiql_source( graphql_endpoint_url: &str, subscriptions_endpoint_url: Option<&str>, ) -> String { let subscriptions_endpoint = if let Some(sub_url) = subscriptions_endpoint_url { sub_url } else { "" }; include_str!("graphiql.html").replace( "", &format!( // language=JavaScript " var JUNIPER_URL = '{graphql_url}'; var JUNIPER_SUBSCRIPTIONS_URL = '{graphql_subscriptions_url}'; {grahiql_js} ", graphql_url = graphql_endpoint_url, graphql_subscriptions_url = subscriptions_endpoint, grahiql_js = include_str!("graphiql.js"), ), ) } juniper-0.16.2/src/http/mod.rs000064400000000000000000001004641046102023000142660ustar 00000000000000//! Utilities for building HTTP endpoints in a library-agnostic manner pub mod graphiql; pub mod playground; use serde::{ de, ser::{self, SerializeMap}, Deserialize, Serialize, }; use crate::{ ast::InputValue, executor::{ExecutionError, ValuesStream}, value::{DefaultScalarValue, ScalarValue}, FieldError, GraphQLError, GraphQLSubscriptionType, GraphQLType, GraphQLTypeAsync, RootNode, Value, Variables, }; /// The expected structure of the decoded JSON document for either POST or GET requests. /// /// For POST, you can use Serde to deserialize the incoming JSON data directly /// into this struct - it derives Deserialize for exactly this reason. /// /// For GET, you will need to parse the query string and extract "query", /// "operationName", and "variables" manually. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct GraphQLRequest where S: ScalarValue, { /// GraphQL query representing this request. pub query: String, /// Optional name of the operation associated with this request. #[serde(rename = "operationName")] pub operation_name: Option, /// Optional variables to execute the GraphQL operation with. // TODO: Use `Variables` instead of `InputValue`? #[serde(bound( deserialize = "InputValue: Deserialize<'de>", serialize = "InputValue: Serialize", ))] pub variables: Option>, } impl GraphQLRequest where S: ScalarValue, { // TODO: Remove in 0.17 `juniper` version. /// Returns the `operation_name` associated with this request. #[deprecated(since = "0.16.0", note = "Use the direct field access instead.")] pub fn operation_name(&self) -> Option<&str> { self.operation_name.as_deref() } /// Returns operation [`Variables`] defined withing this request. pub fn variables(&self) -> Variables { self.variables .as_ref() .and_then(|iv| { iv.to_object_value() .map(|o| o.into_iter().map(|(k, v)| (k.into(), v.clone())).collect()) }) .unwrap_or_default() } /// Construct a new GraphQL request from parts pub fn new( query: String, operation_name: Option, variables: Option>, ) -> Self { Self { query, operation_name, variables, } } /// Execute a GraphQL request synchronously using the specified schema and context /// /// This is a simple wrapper around the `execute_sync` function exposed at the /// top level of this crate. pub fn execute_sync( &self, root_node: &RootNode, context: &QueryT::Context, ) -> GraphQLResponse where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { GraphQLResponse(crate::execute_sync( &self.query, self.operation_name.as_deref(), root_node, &self.variables(), context, )) } /// Execute a GraphQL request using the specified schema and context /// /// This is a simple wrapper around the `execute` function exposed at the /// top level of this crate. pub async fn execute<'a, QueryT, MutationT, SubscriptionT>( &'a self, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'a QueryT::Context, ) -> GraphQLResponse where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLType + Sync, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { let op = self.operation_name.as_deref(); let vars = &self.variables(); let res = crate::execute(&self.query, op, root_node, vars, context).await; GraphQLResponse(res) } } /// Resolve a GraphQL subscription into `Value` using the /// specified schema and context. /// This is a wrapper around the `resolve_into_stream` function exposed at the top /// level of this crate. pub async fn resolve_into_stream<'req, 'rn, 'ctx, 'a, QueryT, MutationT, SubscriptionT, S>( req: &'req GraphQLRequest, root_node: &'rn RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'ctx QueryT::Context, ) -> Result<(Value>, Vec>), GraphQLError> where 'req: 'a, 'rn: 'a, 'ctx: 'a, QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLSubscriptionType, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { let op = req.operation_name.as_deref(); let vars = req.variables(); crate::resolve_into_stream(&req.query, op, root_node, &vars, context).await } /// Simple wrapper around the result from executing a GraphQL query /// /// This struct implements Serialize, so you can simply serialize this /// to JSON and send it over the wire. Use the `is_ok` method to determine /// whether to send a 200 or 400 HTTP status code. #[derive(Clone, Debug, PartialEq)] pub struct GraphQLResponse( Result<(Value, Vec>), GraphQLError>, ); impl GraphQLResponse where S: ScalarValue, { /// Constructs new `GraphQLResponse` using the given result pub fn from_result(r: Result<(Value, Vec>), GraphQLError>) -> Self { Self(r) } /// Constructs an error response outside of the normal execution flow pub fn error(error: FieldError) -> Self { GraphQLResponse(Ok((Value::null(), vec![ExecutionError::at_origin(error)]))) } /// Was the request successful or not? /// /// Note that there still might be errors in the response even though it's /// considered OK. This is by design in GraphQL. pub fn is_ok(&self) -> bool { self.0.is_ok() } } impl Serialize for GraphQLResponse where T: Serialize + ScalarValue, Value: Serialize, ExecutionError: Serialize, { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { match self.0 { Ok((ref res, ref err)) => { let mut map = serializer.serialize_map(None)?; map.serialize_key("data")?; map.serialize_value(res)?; if !err.is_empty() { map.serialize_key("errors")?; map.serialize_value(err)?; } map.end() } Err(ref err) => { let mut map = serializer.serialize_map(Some(1))?; map.serialize_key("errors")?; map.serialize_value(err)?; map.end() } } } } /// Simple wrapper around GraphQLRequest to allow the handling of Batch requests. #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] #[serde(bound = "InputValue: Deserialize<'de>")] pub enum GraphQLBatchRequest where S: ScalarValue, { /// A single operation request. Single(GraphQLRequest), /// A batch operation request. /// /// Empty batch is considered as invalid value, so cannot be deserialized. #[serde(deserialize_with = "deserialize_non_empty_batch")] Batch(Vec>), } fn deserialize_non_empty_batch<'de, D, T>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, T: Deserialize<'de>, { use de::Error as _; let v = Vec::::deserialize(deserializer)?; if v.is_empty() { Err(D::Error::invalid_length( 0, &"non-empty batch of GraphQL requests", )) } else { Ok(v) } } impl GraphQLBatchRequest where S: ScalarValue, { /// Execute a GraphQL batch request synchronously using the specified schema and context /// /// This is a simple wrapper around the `execute_sync` function exposed in GraphQLRequest. pub fn execute_sync<'a, QueryT, MutationT, SubscriptionT>( &'a self, root_node: &'a RootNode, context: &QueryT::Context, ) -> GraphQLBatchResponse where QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { match *self { Self::Single(ref req) => { GraphQLBatchResponse::Single(req.execute_sync(root_node, context)) } Self::Batch(ref reqs) => GraphQLBatchResponse::Batch( reqs.iter() .map(|req| req.execute_sync(root_node, context)) .collect(), ), } } /// Executes a GraphQL request using the specified schema and context /// /// This is a simple wrapper around the `execute` function exposed in /// GraphQLRequest pub async fn execute<'a, QueryT, MutationT, SubscriptionT>( &'a self, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, context: &'a QueryT::Context, ) -> GraphQLBatchResponse where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLSubscriptionType, SubscriptionT::TypeInfo: Sync, S: Send + Sync, { match self { Self::Single(req) => { let resp = req.execute(root_node, context).await; GraphQLBatchResponse::Single(resp) } Self::Batch(reqs) => { let resps = futures::future::join_all( reqs.iter().map(|req| req.execute(root_node, context)), ) .await; GraphQLBatchResponse::Batch(resps) } } } /// The operation names of the request. pub fn operation_names(&self) -> Vec> { match self { Self::Single(req) => vec![req.operation_name.as_deref()], Self::Batch(reqs) => reqs.iter().map(|r| r.operation_name.as_deref()).collect(), } } } /// Simple wrapper around the result (GraphQLResponse) from executing a GraphQLBatchRequest /// /// This struct implements Serialize, so you can simply serialize this /// to JSON and send it over the wire. use the `is_ok` to determine /// wheter to send a 200 or 400 HTTP status code. #[derive(Serialize)] #[serde(untagged)] pub enum GraphQLBatchResponse where S: ScalarValue, { /// Result of a single operation in a GraphQL request. Single(GraphQLResponse), /// Result of a batch operation in a GraphQL request. Batch(Vec>), } impl GraphQLBatchResponse { /// Returns if all the GraphQLResponse in this operation are ok, /// you can use it to determine wheter to send a 200 or 400 HTTP status code. pub fn is_ok(&self) -> bool { match self { Self::Single(resp) => resp.is_ok(), Self::Batch(resps) => resps.iter().all(GraphQLResponse::is_ok), } } } #[cfg(feature = "expose-test-schema")] #[allow(missing_docs)] pub mod tests { use std::time::Duration; use serde_json::Value as Json; use crate::LocalBoxFuture; /// Normalized response content we expect to get back from /// the http framework integration we are testing. #[derive(Debug)] pub struct TestResponse { pub status_code: i32, pub body: Option, pub content_type: String, } /// Normalized way to make requests to the HTTP framework integration we are testing. pub trait HttpIntegration { /// Sends GET HTTP request to this integration with the provided `url` parameters string, /// and returns response returned by this integration. fn get(&self, url: &str) -> TestResponse; /// Sends POST HTTP request to this integration with the provided JSON-encoded `body`, and /// returns response returned by this integration. fn post_json(&self, url: &str, body: &str) -> TestResponse; /// Sends POST HTTP request to this integration with the provided raw GraphQL query as /// `body`, and returns response returned by this integration. fn post_graphql(&self, url: &str, body: &str) -> TestResponse; } #[allow(missing_docs)] pub fn run_http_test_suite(integration: &T) { println!("Running HTTP Test suite for integration"); println!(" - test_simple_get"); test_simple_get(integration); println!(" - test_encoded_get"); test_encoded_get(integration); println!(" - test_get_with_variables"); test_get_with_variables(integration); println!(" - test_post_with_variables"); test_post_with_variables(integration); println!(" - test_simple_post"); test_simple_post(integration); println!(" - test_batched_post"); test_batched_post(integration); println!(" - test_empty_batched_post"); test_empty_batched_post(integration); println!(" - test_invalid_json"); test_invalid_json(integration); println!(" - test_invalid_field"); test_invalid_field(integration); println!(" - test_duplicate_keys"); test_duplicate_keys(integration); println!(" - test_graphql_post"); test_graphql_post(integration); println!(" - test_invalid_graphql_post"); test_invalid_graphql_post(integration); } fn unwrap_json_response(response: &TestResponse) -> Json { serde_json::from_str::( response .body .as_ref() .expect("No data returned from request"), ) .expect("Could not parse JSON object") } fn test_simple_get(integration: &T) { // {hero{name}} let response = integration.get("/?query=%7Bhero%7Bname%7D%7D"); assert_eq!(response.status_code, 200); assert_eq!(response.content_type.as_str(), "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::(r#"{"data": {"hero": {"name": "R2-D2"}}}"#) .expect("Invalid JSON constant in test") ); } fn test_encoded_get(integration: &T) { // query { human(id: "1000") { id, name, appearsIn, homePlanet } } let response = integration.get( "/?query=query%20%7B%20human(id%3A%20%221000%22)%20%7B%20id%2C%20name%2C%20appearsIn%2C%20homePlanet%20%7D%20%7D"); assert_eq!(response.status_code, 200); assert_eq!(response.content_type.as_str(), "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::( r#"{ "data": { "human": { "appearsIn": [ "NEW_HOPE", "EMPIRE", "JEDI" ], "homePlanet": "Tatooine", "name": "Luke Skywalker", "id": "1000" } } }"# ) .expect("Invalid JSON constant in test") ); } fn test_get_with_variables(integration: &T) { // query($id: String!) { human(id: $id) { id, name, appearsIn, homePlanet } } // with variables = { "id": "1000" } let response = integration.get( "/?query=query(%24id%3A%20String!)%20%7B%20human(id%3A%20%24id)%20%7B%20id%2C%20name%2C%20appearsIn%2C%20homePlanet%20%7D%20%7D&variables=%7B%20%22id%22%3A%20%221000%22%20%7D"); assert_eq!(response.status_code, 200); assert_eq!(response.content_type, "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::( r#"{ "data": { "human": { "appearsIn": [ "NEW_HOPE", "EMPIRE", "JEDI" ], "homePlanet": "Tatooine", "name": "Luke Skywalker", "id": "1000" } } }"# ) .expect("Invalid JSON constant in test") ); } fn test_post_with_variables(integration: &T) { let response = integration.post_json( "/", r#"{ "query": "query($id: String!) { human(id: $id) { id, name, appearsIn, homePlanet } }", "variables": {"id": "1000"} }"#, ); assert_eq!(response.status_code, 200); assert_eq!(response.content_type, "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::( r#"{ "data": { "human": { "appearsIn": [ "NEW_HOPE", "EMPIRE", "JEDI" ], "homePlanet": "Tatooine", "name": "Luke Skywalker", "id": "1000" } } }"# ) .expect("Invalid JSON constant in test") ); } fn test_simple_post(integration: &T) { let response = integration.post_json("/", r#"{"query": "{hero{name}}"}"#); assert_eq!(response.status_code, 200); assert_eq!(response.content_type, "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::(r#"{"data": {"hero": {"name": "R2-D2"}}}"#) .expect("Invalid JSON constant in test"), ); } fn test_batched_post(integration: &T) { let response = integration.post_json( "/", r#"[{"query": "{hero{name}}"}, {"query": "{hero{name}}"}]"#, ); assert_eq!(response.status_code, 200); assert_eq!(response.content_type, "application/json"); assert_eq!( unwrap_json_response(&response), serde_json::from_str::( r#"[{"data": {"hero": {"name": "R2-D2"}}}, {"data": {"hero": {"name": "R2-D2"}}}]"#, ) .expect("Invalid JSON constant in test"), ); } fn test_empty_batched_post(integration: &T) { let response = integration.post_json("/", "[]"); assert_eq!(response.status_code, 400); } fn test_invalid_json(integration: &T) { let response = integration.get("/?query=blah"); assert_eq!(response.status_code, 400); let response = integration.post_json("/", r#"blah"#); assert_eq!(response.status_code, 400); } fn test_invalid_field(integration: &T) { // {hero{blah}} let response = integration.get("/?query=%7Bhero%7Bblah%7D%7D"); assert_eq!(response.status_code, 400); let response = integration.post_json("/", r#"{"query": "{hero{blah}}"}"#); assert_eq!(response.status_code, 400); } fn test_duplicate_keys(integration: &T) { // {hero{name}} let response = integration.get("/?query=%7B%22query%22%3A%20%22%7Bhero%7Bname%7D%7D%22%2C%20%22query%22%3A%20%22%7Bhero%7Bname%7D%7D%22%7D"); assert_eq!(response.status_code, 400); let response = integration.post_json("/", r#"{"query": "{hero{name}}", "query": "{hero{name}}"}"#); assert_eq!(response.status_code, 400); } fn test_graphql_post(integration: &T) { let resp = integration.post_graphql("/", r#"{hero{name}}"#); assert_eq!(resp.status_code, 200); assert_eq!(resp.content_type, "application/json"); assert_eq!( unwrap_json_response(&resp), serde_json::from_str::(r#"{"data": {"hero": {"name": "R2-D2"}}}"#) .expect("Invalid JSON constant in test"), ); } fn test_invalid_graphql_post(integration: &T) { let resp = integration.post_graphql("/", r#"{hero{name}"#); assert_eq!(resp.status_code, 400); } /// Normalized way to make requests to the WebSocket framework integration we are testing. pub trait WsIntegration { /// Runs a test with the given messages fn run( &self, messages: Vec, ) -> LocalBoxFuture>; } /// WebSocket framework integration message. pub enum WsIntegrationMessage { /// Send a message through a WebSocket. Send(Json), /// Expects a message to come through a WebSocket, with the specified timeout. Expect(Json, Duration), } /// Default value in milliseconds for how long to wait for an incoming WebSocket message. pub const WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT: Duration = Duration::from_millis(100); /// Integration tests for the [legacy `graphql-ws` GraphQL over WebSocket Protocol][old]. /// /// [old]: https://github.com/apollographql/subscriptions-transport-ws/blob/v0.11.0/PROTOCOL.md pub mod graphql_ws { use serde_json::json; use super::{WsIntegration, WsIntegrationMessage, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT}; #[allow(missing_docs)] pub async fn run_test_suite(integration: &T) { println!("Running `graphql-ws` test suite for integration"); println!(" - graphql_ws::test_simple_subscription"); test_simple_subscription(integration).await; println!(" - graphql_ws::test_invalid_json"); test_invalid_json(integration).await; println!(" - graphql_ws::test_invalid_query"); test_invalid_query(integration).await; } async fn test_simple_subscription(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({ "type": "connection_init", "payload": {}, })), WsIntegrationMessage::Expect( json!({"type": "connection_ack"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Expect( json!({"type": "ka"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({ "id": "1", "type": "start", "payload": { "variables": {}, "extensions": {}, "operationName": null, "query": "subscription { asyncHuman { id, name, homePlanet } }", }, })), WsIntegrationMessage::Expect( json!({ "type": "data", "id": "1", "payload": { "data": { "asyncHuman": { "id": "1000", "name": "Luke Skywalker", "homePlanet": "Tatooine", }, }, }, }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } async fn test_invalid_json(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({"whatever": "invalid value"})), WsIntegrationMessage::Expect( json!({ "type": "connection_error", "payload": { "message": "`serde` error: missing field `type` at line 1 column 28", }, }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } async fn test_invalid_query(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({ "type": "connection_init", "payload": {}, })), WsIntegrationMessage::Expect( json!({"type": "connection_ack"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Expect( json!({"type": "ka"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({ "id": "1", "type": "start", "payload": { "variables": {}, "extensions": {}, "operationName": null, "query": "subscription { asyncHuman }", }, })), WsIntegrationMessage::Expect( json!({ "type": "error", "id": "1", "payload": [{ "message": "Field \"asyncHuman\" of type \"Human!\" must have a selection \ of subfields. Did you mean \"asyncHuman { ... }\"?", "locations": [{ "line": 1, "column": 16, }], }], }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } } /// Integration tests for the [new `graphql-transport-ws` GraphQL over WebSocket Protocol][new]. /// /// [new]: https://github.com/enisdenjo/graphql-ws/blob/v5.14.0/PROTOCOL.md pub mod graphql_transport_ws { use serde_json::json; use super::{WsIntegration, WsIntegrationMessage, WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT}; #[allow(missing_docs)] pub async fn run_test_suite(integration: &T) { println!("Running `graphql-transport-ws` test suite for integration"); println!(" - graphql_ws::test_simple_subscription"); test_simple_subscription(integration).await; println!(" - graphql_ws::test_invalid_json"); test_invalid_json(integration).await; println!(" - graphql_ws::test_invalid_query"); test_invalid_query(integration).await; } async fn test_simple_subscription(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({ "type": "connection_init", "payload": {}, })), WsIntegrationMessage::Expect( json!({"type": "connection_ack"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Expect( json!({"type": "pong"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({"type": "ping"})), WsIntegrationMessage::Expect( json!({"type": "pong"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({ "id": "1", "type": "subscribe", "payload": { "variables": {}, "extensions": {}, "operationName": null, "query": "subscription { asyncHuman { id, name, homePlanet } }", }, })), WsIntegrationMessage::Expect( json!({ "id": "1", "type": "next", "payload": { "data": { "asyncHuman": { "id": "1000", "name": "Luke Skywalker", "homePlanet": "Tatooine", }, }, }, }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } async fn test_invalid_json(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({"whatever": "invalid value"})), WsIntegrationMessage::Expect( json!({ "code": 4400, "description": "`serde` error: missing field `type` at line 1 column 28", }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } async fn test_invalid_query(integration: &T) { let messages = vec![ WsIntegrationMessage::Send(json!({ "type": "connection_init", "payload": {}, })), WsIntegrationMessage::Expect( json!({"type": "connection_ack"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Expect( json!({"type": "pong"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({"type": "ping"})), WsIntegrationMessage::Expect( json!({"type": "pong"}), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), WsIntegrationMessage::Send(json!({ "id": "1", "type": "subscribe", "payload": { "variables": {}, "extensions": {}, "operationName": null, "query": "subscription { asyncHuman }", }, })), WsIntegrationMessage::Expect( json!({ "type": "error", "id": "1", "payload": [{ "message": "Field \"asyncHuman\" of type \"Human!\" must have a selection \ of subfields. Did you mean \"asyncHuman { ... }\"?", "locations": [{ "line": 1, "column": 16, }], }], }), WS_INTEGRATION_EXPECT_DEFAULT_TIMEOUT, ), ]; integration.run(messages).await.unwrap(); } } } juniper-0.16.2/src/http/playground.html000064400000000000000000000452311046102023000162130ustar 00000000000000 GraphQL Playground
Loading GraphQL Playground
juniper-0.16.2/src/http/playground.rs000064400000000000000000000010641046102023000156670ustar 00000000000000//! Utility module to generate a GraphQL Playground interface. /// Generate the HTML source to show a GraphQL Playground interface. pub fn playground_source( graphql_endpoint_url: &str, subscriptions_endpoint_url: Option<&str>, ) -> String { let subscriptions_endpoint = if let Some(sub_url) = subscriptions_endpoint_url { sub_url } else { graphql_endpoint_url }; include_str!("playground.html") .replace("JUNIPER_URL", graphql_endpoint_url) .replace("JUNIPER_SUBSCRIPTIONS_URL", subscriptions_endpoint) } juniper-0.16.2/src/integrations/anyhow.rs000064400000000000000000000075031046102023000165430ustar 00000000000000//! GraphQL support for [`anyhow::Error`]. //! //! # Example //! //! ```rust //! # use std::backtrace::Backtrace; //! use anyhow::anyhow; //! # use juniper::graphql_object; //! //! struct Root; //! //! #[graphql_object] //! impl Root { //! fn err() -> anyhow::Result { //! Err(anyhow!("errored!")) //! } //! } //! ``` //! //! # Backtrace //! //! Backtrace is supported in the same way as [`anyhow`] crate does: //! > If using the nightly channel, or stable with `features = ["backtrace"]`, a backtrace is //! > captured and printed with the error if the underlying error type does not already provide its //! > own. In order to see backtraces, they must be enabled through the environment variables //! > described in [`std::backtrace`]: //! > - If you want panics and errors to both have backtraces, set `RUST_BACKTRACE=1`; //! > - If you want only errors to have backtraces, set `RUST_LIB_BACKTRACE=1`; //! > - If you want only panics to have backtraces, set `RUST_BACKTRACE=1` and //! > `RUST_LIB_BACKTRACE=0`. use crate::{FieldError, IntoFieldError, ScalarValue, Value}; impl IntoFieldError for anyhow::Error { fn into_field_error(self) -> FieldError { #[cfg(any(nightly, feature = "backtrace"))] let extensions = { let backtrace = self.backtrace().to_string(); if backtrace == "disabled backtrace" { Value::Null } else { let mut obj = crate::value::Object::with_capacity(1); _ = obj.add_field( "backtrace", Value::List( backtrace .split('\n') .map(|line| Value::Scalar(line.to_owned().into())) .collect(), ), ); Value::Object(obj) } }; #[cfg(not(any(nightly, feature = "backtrace")))] let extensions = Value::Null; FieldError::new(self, extensions) } } #[cfg(test)] mod test { use std::env; use anyhow::anyhow; use serial_test::serial; use crate::{ execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, EmptyMutation, EmptySubscription, RootNode, }; #[tokio::test] #[serial] async fn simple() { struct Root; #[graphql_object] impl Root { fn err() -> anyhow::Result { Err(anyhow!("errored!")) } } let prev_env = env::var("RUST_BACKTRACE").ok(); env::set_var("RUST_BACKTRACE", "1"); const DOC: &str = r#"{ err }"#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let res = execute(DOC, None, &schema, &graphql_vars! {}, &()).await; assert!(res.is_ok(), "failed: {:?}", res.unwrap_err()); let (val, errs) = res.unwrap(); assert_eq!(val, graphql_value!(null)); assert_eq!(errs.len(), 1, "too many errors: {errs:?}"); let err = errs.first().unwrap(); assert_eq!(*err.location(), SourcePosition::new(14, 1, 12)); assert_eq!(err.path(), &["err"]); let err = err.error(); assert_eq!(err.message(), "errored!"); #[cfg(not(any(nightly, feature = "backtrace")))] assert_eq!(err.extensions(), &graphql_value!(null)); #[cfg(any(nightly, feature = "backtrace"))] assert_eq!( err.extensions() .as_object_value() .map(|ext| ext.contains_field("backtrace")), Some(true), "no `backtrace` in extensions: {err:?}", ); if let Some(val) = prev_env { env::set_var("RUST_BACKTRACE", val); } } } juniper-0.16.2/src/integrations/bigdecimal.rs000064400000000000000000000102451046102023000173130ustar 00000000000000//! GraphQL support for [`bigdecimal`] crate types. //! //! # Supported types //! //! | Rust type | GraphQL scalar | //! |----------------|----------------| //! | [`BigDecimal`] | `BigDecimal` | //! //! [`BigDecimal`]: bigdecimal::BigDecimal use std::str::FromStr as _; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Big decimal type. /// /// Allows storing any real number to arbitrary precision; which avoids common /// floating point errors (such as 0.1 + 0.2 ≠ 0.3) at the cost of complexity. /// /// Always serializes as `String`. But may be deserialized from `Int` and /// `Float` values too. It's not recommended to deserialize from a `Float` /// directly, as the floating point representation may be unexpected. /// /// See also [`bigdecimal`] crate for details. /// /// [`bigdecimal`]: https://docs.rs/bigdecimal #[graphql_scalar( with = bigdecimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/bigdecimal", )] type BigDecimal = bigdecimal::BigDecimal; mod bigdecimal_scalar { use super::*; pub(super) fn to_output(v: &BigDecimal) -> Value { Value::scalar(v.to_string()) } pub(super) fn from_input(v: &InputValue) -> Result { if let Some(i) = v.as_int_value() { Ok(BigDecimal::from(i)) } else if let Some(f) = v.as_float_value() { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); BigDecimal::from_str(buf.format(f)) .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}")) } else { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { BigDecimal::from_str(s) .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {e}")) }) } } } #[cfg(test)] mod test { use std::str::FromStr as _; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::BigDecimal; #[test] fn parses_correct_input() { for (input, expected) in [ (graphql_input_value!("4.20"), "4.20"), (graphql_input_value!("0"), "0"), ( graphql_input_value!("999999999999.999999999"), "999999999999.999999999", ), ( graphql_input_value!("87553378877997984345"), "87553378877997984345", ), (graphql_input_value!(123), "123"), (graphql_input_value!(0), "0"), (graphql_input_value!(43.44), "43.44"), ] { let input: InputValue = input; let parsed = BigDecimal::from_input_value(&input); let expected = BigDecimal::from_str(expected).unwrap(); assert!( parsed.is_ok(), "failed to parse `{input:?}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {input:?}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!(""), graphql_input_value!("0,0"), graphql_input_value!("12,"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("i'm not even a number"), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = BigDecimal::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for raw in [ "4.20", "0", "999999999999.999999999", "87553378877997984345", "123", "43.44", ] { let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } } } juniper-0.16.2/src/integrations/bson.rs000064400000000000000000000046311046102023000161760ustar 00000000000000//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types. use crate::{graphql_scalar, InputValue, ScalarValue, Value}; #[graphql_scalar(with = object_id, parse_token(String))] type ObjectId = bson::oid::ObjectId; mod object_id { use super::*; pub(super) fn to_output(v: &ObjectId) -> Value { Value::scalar(v.to_hex()) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {e}")) }) } } #[graphql_scalar(with = utc_date_time, parse_token(String))] type UtcDateTime = bson::DateTime; mod utc_date_time { use super::*; pub(super) fn to_output(v: &UtcDateTime) -> Value { Value::scalar( (*v).try_to_rfc3339_string() .unwrap_or_else(|e| panic!("failed to format `UtcDateTime` as RFC3339: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { UtcDateTime::parse_rfc3339_str(s) .map_err(|e| format!("Failed to parse `UtcDateTime`: {e}")) }) } } #[cfg(test)] mod test { use bson::{oid::ObjectId, DateTime as UtcDateTime}; use crate::{graphql_input_value, FromInputValue, InputValue}; #[test] fn objectid_from_input() { let raw = "53e37d08776f724e42000000"; let input: InputValue = graphql_input_value!((raw)); let parsed: ObjectId = FromInputValue::from_input_value(&input).unwrap(); let id = ObjectId::parse_str(raw).unwrap(); assert_eq!(parsed, id); } #[test] fn utcdatetime_from_input() { use chrono::{DateTime, Utc}; let raw = "2020-03-23T17:38:32.446+00:00"; let input: InputValue = graphql_input_value!((raw)); let parsed: UtcDateTime = FromInputValue::from_input_value(&input).unwrap(); let date_time = UtcDateTime::from_chrono( DateTime::parse_from_rfc3339(raw) .unwrap() .with_timezone(&Utc), ); assert_eq!(parsed, date_time); } } juniper-0.16.2/src/integrations/chrono.rs000064400000000000000000000662251046102023000165340ustar 00000000000000//! GraphQL support for [`chrono`] crate types. //! //! # Supported types //! //! | Rust type | Format | GraphQL scalar | //! |-------------------|-----------------------|-------------------| //! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | //! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | //! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | //! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] | //! //! [`DateTime`]: chrono::DateTime //! [`NaiveDate`]: chrono::naive::NaiveDate //! [`NaiveDateTime`]: chrono::naive::NaiveDateTime //! [`NaiveTime`]: chrono::naive::NaiveTime //! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 //! [s1]: https://graphql-scalars.dev/docs/scalars/date //! [s2]: https://graphql-scalars.dev/docs/scalars/local-time //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time use std::fmt; use chrono::{FixedOffset, TimeZone}; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Date in the proleptic Gregorian calendar (without time zone). /// /// Represents a description of the date (as used for birthdays, for example). /// It cannot represent an instant on the time-line. /// /// [`Date` scalar][1] compliant. /// /// See also [`chrono::NaiveDate`][2] for details. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html #[graphql_scalar( with = date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", )] pub type Date = chrono::NaiveDate; mod date { use super::*; /// Format of a [`Date` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date const FORMAT: &str = "%Y-%m-%d"; pub(super) fn to_output(v: &Date) -> Value where S: ScalarValue, { Value::scalar(v.format(FORMAT).to_string()) } pub(super) fn from_input(v: &InputValue) -> Result where S: ScalarValue, { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}")) }) } } /// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]` /// format. /// /// All minutes are assumed to have exactly 60 seconds; no attempt is made to /// handle leap seconds (either positive or negative). /// /// [`LocalTime` scalar][1] compliant. /// /// See also [`chrono::NaiveTime`][2] for details. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html #[graphql_scalar( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", )] pub type LocalTime = chrono::NaiveTime; mod local_time { use chrono::Timelike as _; use super::*; /// Full format of a [`LocalTime` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT: &str = "%H:%M:%S%.3f"; /// Format of a [`LocalTime` scalar][1] without milliseconds. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_MILLIS: &str = "%H:%M:%S"; /// Format of a [`LocalTime` scalar][1] without seconds. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; pub(super) fn to_output(v: &LocalTime) -> Value where S: ScalarValue, { Value::scalar( if v.nanosecond() == 0 { v.format(FORMAT_NO_MILLIS) } else { v.format(FORMAT) } .to_string(), ) } pub(super) fn from_input(v: &InputValue) -> Result where S: ScalarValue, { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing // error to be most informative. LocalTime::parse_from_str(s, FORMAT_NO_MILLIS) .or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse_from_str(s, FORMAT)) .map_err(|e| format!("Invalid `LocalTime`: {e}")) }) } } /// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format. /// /// See also [`chrono::NaiveDateTime`][1] for details. /// /// [1]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html #[graphql_scalar(with = local_date_time, parse_token(String))] pub type LocalDateTime = chrono::NaiveDateTime; mod local_date_time { use super::*; /// Format of a `LocalDateTime` scalar. const FORMAT: &str = "%Y-%m-%d %H:%M:%S"; pub(super) fn to_output(v: &LocalDateTime) -> Value where S: ScalarValue, { Value::scalar(v.format(FORMAT).to_string()) } pub(super) fn from_input(v: &InputValue) -> Result where S: ScalarValue, { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { LocalDateTime::parse_from_str(s, FORMAT) .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) }) } } /// Combined date and time (with time zone) in [RFC 3339][0] format. /// /// Represents a description of an exact instant on the time-line (such as the /// instant that a user account was created). /// /// [`DateTime` scalar][1] compliant. /// /// See also [`chrono::DateTime`][2] for details. /// /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html #[graphql_scalar( with = date_time, parse_token(String), where( Tz: TimeZone + FromFixedOffset, Tz::Offset: fmt::Display, ) )] pub type DateTime = chrono::DateTime; mod date_time { use chrono::{SecondsFormat, Utc}; use super::*; pub(super) fn to_output(v: &DateTime) -> Value where S: ScalarValue, Tz: chrono::TimeZone, Tz::Offset: fmt::Display, { Value::scalar( v.with_timezone(&Utc) .to_rfc3339_opts(SecondsFormat::AutoSi, true), ) } pub(super) fn from_input(v: &InputValue) -> Result, String> where S: ScalarValue, Tz: TimeZone + FromFixedOffset, { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::::parse_from_rfc3339(s) .map_err(|e| format!("Invalid `DateTime`: {e}")) .map(FromFixedOffset::from_fixed_offset) }) } } /// Trait allowing to implement a custom [`TimeZone`], which preserves its /// [`TimeZone`] information when parsed in a [`DateTime`] GraphQL scalar. /// /// # Example /// /// Creating a custom [CET] [`TimeZone`] using [`chrono-tz`] crate. This is /// required because [`chrono-tz`] uses enum to represent all [`TimeZone`]s, so /// we have no knowledge of the concrete underlying [`TimeZone`] on the type /// level. /// /// ```rust /// # use chrono::{FixedOffset, TimeZone}; /// # use juniper::{ /// # integrations::chrono::{FromFixedOffset, DateTime}, /// # graphql_object, /// # }; /// # /// #[derive(Clone, Copy)] /// struct CET; /// /// impl TimeZone for CET { /// type Offset = ::Offset; /// /// fn from_offset(_: &Self::Offset) -> Self { /// CET /// } /// /// fn offset_from_local_date( /// &self, /// local: &chrono::NaiveDate, /// ) -> chrono::LocalResult { /// chrono_tz::CET.offset_from_local_date(local) /// } /// /// fn offset_from_local_datetime( /// &self, /// local: &chrono::NaiveDateTime, /// ) -> chrono::LocalResult { /// chrono_tz::CET.offset_from_local_datetime(local) /// } /// /// fn offset_from_utc_date(&self, utc: &chrono::NaiveDate) -> Self::Offset { /// chrono_tz::CET.offset_from_utc_date(utc) /// } /// /// fn offset_from_utc_datetime(&self, utc: &chrono::NaiveDateTime) -> Self::Offset { /// chrono_tz::CET.offset_from_utc_datetime(utc) /// } /// } /// /// impl FromFixedOffset for CET { /// fn from_fixed_offset(dt: DateTime) -> DateTime { /// dt.with_timezone(&CET) /// } /// } /// /// struct Root; /// /// #[graphql_object] /// impl Root { /// fn pass_date_time(dt: DateTime) -> DateTime { /// dt /// } /// } /// ``` /// /// [`chrono-tz`]: chrono_tz /// [CET]: https://en.wikipedia.org/wiki/Central_European_Time pub trait FromFixedOffset: TimeZone { /// Converts the given [`DateTime`]`<`[`FixedOffset`]`>` into a /// [`DateTime`]``. fn from_fixed_offset(dt: DateTime) -> DateTime; } impl FromFixedOffset for FixedOffset { fn from_fixed_offset(dt: DateTime) -> DateTime { dt } } impl FromFixedOffset for chrono::Utc { fn from_fixed_offset(dt: DateTime) -> DateTime { dt.into() } } #[cfg(feature = "chrono-clock")] impl FromFixedOffset for chrono::Local { fn from_fixed_offset(dt: DateTime) -> DateTime { dt.into() } } #[cfg(feature = "chrono-tz")] impl FromFixedOffset for chrono_tz::Tz { fn from_fixed_offset(dt: DateTime) -> DateTime { dt.with_timezone(&chrono_tz::UTC) } } #[cfg(test)] mod date_test { use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::Date; #[test] fn parses_correct_input() { for (raw, expected) in [ ("1996-12-19", Date::from_ymd_opt(1996, 12, 19)), ("1564-01-30", Date::from_ymd_opt(1564, 01, 30)), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = Date::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected.unwrap(), "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("1996-13-19"), graphql_input_value!("1564-01-61"), graphql_input_value!("2021-11-31"), graphql_input_value!("11-31"), graphql_input_value!("2021-11"), graphql_input_value!("2021"), graphql_input_value!("31"), graphql_input_value!("i'm not even a date"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = Date::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( Date::from_ymd_opt(1996, 12, 19), graphql_input_value!("1996-12-19"), ), ( Date::from_ymd_opt(1564, 01, 30), graphql_input_value!("1564-01-30"), ), ( Date::from_ymd_opt(2020, 01, 01), graphql_input_value!("2020-01-01"), ), ] { let val = val.unwrap(); let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod local_time_test { use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::LocalTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ("14:23:43", LocalTime::from_hms_opt(14, 23, 43)), ("14:00:00", LocalTime::from_hms_opt(14, 00, 00)), ("14:00", LocalTime::from_hms_opt(14, 00, 00)), ("14:32", LocalTime::from_hms_opt(14, 32, 00)), ("14:00:00.000", LocalTime::from_hms_opt(14, 00, 00)), ( "14:23:43.345", LocalTime::from_hms_milli_opt(14, 23, 43, 345), ), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = LocalTime::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected.unwrap(), "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("23:78:43"), graphql_input_value!("23:78:"), graphql_input_value!("23:18:99"), graphql_input_value!("23:18:22."), graphql_input_value!("22.03"), graphql_input_value!("24:00"), graphql_input_value!("24:00:00"), graphql_input_value!("24:00:00.000"), graphql_input_value!("i'm not even a time"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = LocalTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( LocalTime::from_hms_micro_opt(1, 2, 3, 4005), graphql_input_value!("01:02:03.004"), ), ( LocalTime::from_hms_opt(0, 0, 0), graphql_input_value!("00:00:00"), ), ( LocalTime::from_hms_opt(12, 0, 0), graphql_input_value!("12:00:00"), ), ( LocalTime::from_hms_opt(1, 2, 3), graphql_input_value!("01:02:03"), ), ] { let val = val.unwrap(); let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod local_date_time_test { use chrono::naive::{NaiveDate, NaiveTime}; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::LocalDateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "1996-12-19 14:23:43", LocalDateTime::new( NaiveDate::from_ymd_opt(1996, 12, 19).unwrap(), NaiveTime::from_hms_opt(14, 23, 43).unwrap(), ), ), ( "1564-01-30 14:00:00", LocalDateTime::new( NaiveDate::from_ymd_opt(1564, 1, 30).unwrap(), NaiveTime::from_hms_opt(14, 00, 00).unwrap(), ), ), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = LocalDateTime::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("56:34:22.000"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("1996-12-19 14:23:43Z"), graphql_input_value!("1996-12-19 14:23:43.543"), graphql_input_value!("1996-12-19 14:23"), graphql_input_value!("1996-12-19 14:23:"), graphql_input_value!("1996-12-19 23:78:43"), graphql_input_value!("1996-12-19 23:18:99"), graphql_input_value!("1996-12-19 24:00:00"), graphql_input_value!("1996-12-19 99:02:13"), graphql_input_value!("i'm not even a datetime"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = LocalDateTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( LocalDateTime::new( NaiveDate::from_ymd_opt(1996, 12, 19).unwrap(), NaiveTime::from_hms_opt(0, 0, 0).unwrap(), ), graphql_input_value!("1996-12-19 00:00:00"), ), ( LocalDateTime::new( NaiveDate::from_ymd_opt(1564, 1, 30).unwrap(), NaiveTime::from_hms_opt(14, 0, 0).unwrap(), ), graphql_input_value!("1564-01-30 14:00:00"), ), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod date_time_test { use chrono::{ naive::{NaiveDate, NaiveDateTime, NaiveTime}, FixedOffset, }; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::DateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "2014-11-28T21:00:09+09:00", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_opt(12, 0, 9).unwrap(), ), FixedOffset::east_opt(9 * 3600).unwrap(), ), ), ( "2014-11-28T21:00:09Z", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_opt(21, 0, 9).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), ), ( "2014-11-28 21:00:09z", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_opt(21, 0, 9).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), ), ( "2014-11-28T21:00:09+00:00", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_opt(21, 0, 9).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), ), ( "2014-11-28T21:00:09.05+09:00", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_milli_opt(12, 0, 9, 50).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), ), ( "2014-11-28 21:00:09.05+09:00", DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(2014, 11, 28).unwrap(), NaiveTime::from_hms_milli_opt(12, 0, 9, 50).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), ), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = DateTime::::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("56:34:22.000"), graphql_input_value!("1996-12-1914:23:43"), graphql_input_value!("1996-12-19Q14:23:43Z"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("1996-12-19T14:23:43ZZ"), graphql_input_value!("1996-12-19T14:23:43.543"), graphql_input_value!("1996-12-19T14:23"), graphql_input_value!("1996-12-19T14:23:1"), graphql_input_value!("1996-12-19T14:23:"), graphql_input_value!("1996-12-19T23:78:43Z"), graphql_input_value!("1996-12-19T23:18:99Z"), graphql_input_value!("1996-12-19T24:00:00Z"), graphql_input_value!("1996-12-19T99:02:13Z"), graphql_input_value!("1996-12-19T99:02:13Z"), graphql_input_value!("1996-12-19T12:02:13+4444444"), graphql_input_value!("i'm not even a datetime"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = DateTime::::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(1996, 12, 19).unwrap(), NaiveTime::from_hms_opt(0, 0, 0).unwrap(), ), FixedOffset::east_opt(0).unwrap(), ), graphql_input_value!("1996-12-19T00:00:00Z"), ), ( DateTime::::from_naive_utc_and_offset( NaiveDateTime::new( NaiveDate::from_ymd_opt(1564, 1, 30).unwrap(), NaiveTime::from_hms_milli_opt(5, 0, 0, 123).unwrap(), ), FixedOffset::east_opt(9 * 3600).unwrap(), ), graphql_input_value!("1564-01-30T05:00:00.123Z"), ), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod integration_test { use crate::{ execute, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; use super::{Date, DateTime, FixedOffset, FromFixedOffset, LocalDateTime, LocalTime, TimeZone}; #[tokio::test] async fn serializes() { #[derive(Clone, Copy)] struct CET; impl TimeZone for CET { type Offset = ::Offset; fn from_offset(_: &Self::Offset) -> Self { CET } fn offset_from_local_date( &self, local: &chrono::NaiveDate, ) -> chrono::LocalResult { chrono_tz::CET.offset_from_local_date(local) } fn offset_from_local_datetime( &self, local: &chrono::NaiveDateTime, ) -> chrono::LocalResult { chrono_tz::CET.offset_from_local_datetime(local) } fn offset_from_utc_date(&self, utc: &chrono::NaiveDate) -> Self::Offset { chrono_tz::CET.offset_from_utc_date(utc) } fn offset_from_utc_datetime(&self, utc: &chrono::NaiveDateTime) -> Self::Offset { chrono_tz::CET.offset_from_utc_datetime(utc) } } impl FromFixedOffset for CET { fn from_fixed_offset(dt: DateTime) -> DateTime { dt.with_timezone(&CET) } } struct Root; #[graphql_object] impl Root { fn date() -> Date { Date::from_ymd_opt(2015, 3, 14).unwrap() } fn local_time() -> LocalTime { LocalTime::from_hms_opt(16, 7, 8).unwrap() } fn local_date_time() -> LocalDateTime { LocalDateTime::new( Date::from_ymd_opt(2016, 7, 8).unwrap(), LocalTime::from_hms_opt(9, 10, 11).unwrap(), ) } fn date_time() -> DateTime { DateTime::from_naive_utc_and_offset( LocalDateTime::new( Date::from_ymd_opt(1996, 12, 20).unwrap(), LocalTime::from_hms_opt(0, 39, 57).unwrap(), ), chrono::Utc, ) } fn pass_date_time(dt: DateTime) -> DateTime { dt } fn transform_date_time(dt: DateTime) -> DateTime { dt.with_timezone(&chrono::Utc) } } const DOC: &str = r#"{ date localTime localDateTime dateTime, passDateTime(dt: "2014-11-28T21:00:09+09:00") transformDateTime(dt: "2014-11-28T21:00:09+09:00") }"#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({ "date": "2015-03-14", "localTime": "16:07:08", "localDateTime": "2016-07-08 09:10:11", "dateTime": "1996-12-20T00:39:57Z", "passDateTime": "2014-11-28T12:00:09Z", "transformDateTime": "2014-11-28T12:00:09Z", }), vec![], )), ); } } juniper-0.16.2/src/integrations/chrono_tz.rs000064400000000000000000000056221046102023000172430ustar 00000000000000//! GraphQL support for [`chrono-tz`] crate types. //! //! # Supported types //! //! | Rust type | Format | GraphQL scalar | //! |-----------|--------------------|----------------| //! | [`Tz`] | [IANA database][1] | `TimeZone` | //! //! [`chrono-tz`]: chrono_tz //! [`Tz`]: chrono_tz::Tz //! [1]: http://www.iana.org/time-zones use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Timezone based on [`IANA` database][1]. /// /// See ["List of tz database time zones"][2] `TZ database name` column for /// available names. /// /// See also [`chrono_tz::Tz`][3] for detals. /// /// [1]: https://www.iana.org/time-zones /// [2]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones /// [3]: https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html #[graphql_scalar(with = tz, parse_token(String))] pub type TimeZone = chrono_tz::Tz; mod tz { use super::*; pub(super) fn to_output(v: &TimeZone) -> Value { Value::scalar(v.name().to_owned()) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { s.parse::() .map_err(|e| format!("Failed to parse `TimeZone`: {e}")) }) } } #[cfg(test)] mod test { use super::TimeZone; mod from_input_value { use super::TimeZone; use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError}; fn tz_input_test(raw: &'static str, expected: Result) { let input: InputValue = graphql_input_value!((raw)); let parsed = FromInputValue::from_input_value(&input); assert_eq!( parsed.as_ref(), expected.map_err(IntoFieldError::into_field_error).as_ref(), ); } #[test] fn europe_zone() { tz_input_test("Europe/London", Ok(chrono_tz::Europe::London)); } #[test] fn etc_minus() { tz_input_test("Etc/GMT-3", Ok(chrono_tz::Etc::GMTMinus3)); } mod invalid { use super::tz_input_test; #[test] fn forward_slash() { tz_input_test( "Abc/Xyz", Err("Failed to parse `TimeZone`: received invalid timezone"), ); } #[test] fn number() { tz_input_test( "8086", Err("Failed to parse `TimeZone`: received invalid timezone"), ); } #[test] fn no_forward_slash() { tz_input_test( "AbcXyz", Err("Failed to parse `TimeZone`: received invalid timezone"), ); } } } } juniper-0.16.2/src/integrations/mod.rs000064400000000000000000000007531046102023000160150ustar 00000000000000//! Provides GraphQLType implementations for some external types #[cfg(feature = "anyhow")] pub mod anyhow; #[cfg(feature = "bigdecimal")] pub mod bigdecimal; #[cfg(feature = "bson")] pub mod bson; #[cfg(feature = "chrono")] pub mod chrono; #[cfg(feature = "chrono-tz")] pub mod chrono_tz; #[cfg(feature = "rust_decimal")] pub mod rust_decimal; #[doc(hidden)] pub mod serde; #[cfg(feature = "time")] pub mod time; #[cfg(feature = "url")] pub mod url; #[cfg(feature = "uuid")] pub mod uuid; juniper-0.16.2/src/integrations/rust_decimal.rs000064400000000000000000000077051046102023000177150ustar 00000000000000//! GraphQL support for [`rust_decimal`] crate types. //! //! # Supported types //! //! | Rust type | GraphQL scalar | //! |-------------|----------------| //! | [`Decimal`] | `Decimal` | //! //! [`Decimal`]: rust_decimal::Decimal use std::str::FromStr as _; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// 128 bit representation of a fixed-precision decimal number. /// /// The finite set of values of `Decimal` scalar are of the form /// m / 10e, where m is an integer such that /// -296 < m < 296, and e is an integer between 0 and 28 /// inclusive. /// /// Always serializes as `String`. But may be deserialized from `Int` and /// `Float` values too. It's not recommended to deserialize from a `Float` /// directly, as the floating point representation may be unexpected. /// /// See also [`rust_decimal`] crate for details. /// /// [`rust_decimal`]: https://docs.rs/rust_decimal #[graphql_scalar( with = rust_decimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/rust_decimal", )] type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { use super::*; pub(super) fn to_output(v: &Decimal) -> Value { Value::scalar(v.to_string()) } pub(super) fn from_input(v: &InputValue) -> Result { if let Some(i) = v.as_int_value() { Ok(Decimal::from(i)) } else if let Some(f) = v.as_float_value() { Decimal::try_from(f).map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}")) } else { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { Decimal::from_str(s) .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}")) }) } } } #[cfg(test)] mod test { use std::str::FromStr as _; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::Decimal; #[test] fn parses_correct_input() { for (input, expected) in [ (graphql_input_value!("4.20"), "4.20"), (graphql_input_value!("0"), "0"), (graphql_input_value!("999.999999999"), "999.999999999"), (graphql_input_value!("875533788"), "875533788"), (graphql_input_value!(123), "123"), (graphql_input_value!(0), "0"), (graphql_input_value!(43.44), "43.44"), ] { let input: InputValue = input; let parsed = Decimal::from_input_value(&input); let expected = Decimal::from_str(expected).unwrap(); assert!( parsed.is_ok(), "failed to parse `{input:?}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {input:?}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!(""), graphql_input_value!("0,0"), graphql_input_value!("12,"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("99999999999999999999999999999999999999"), graphql_input_value!("99999999999999999999999999999999999999.99"), graphql_input_value!("i'm not even a number"), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = Decimal::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } } } juniper-0.16.2/src/integrations/serde.rs000064400000000000000000000333361046102023000163430ustar 00000000000000use std::{fmt, marker::PhantomData}; use indexmap::IndexMap; use serde::{ de::{self, Deserializer, IntoDeserializer as _}, ser::{SerializeMap as _, Serializer}, serde_if_integer128, Deserialize, Serialize, }; use crate::{ ast::InputValue, executor::ExecutionError, parser::{ParseError, SourcePosition, Spanning}, validation::RuleError, DefaultScalarValue, GraphQLError, Object, Value, }; impl Serialize for ExecutionError { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(4))?; map.serialize_key("message")?; map.serialize_value(self.error().message())?; let locations = vec![self.location()]; map.serialize_key("locations")?; map.serialize_value(&locations)?; map.serialize_key("path")?; map.serialize_value(self.path())?; if !self.error().extensions().is_null() { map.serialize_key("extensions")?; map.serialize_value(self.error().extensions())?; } map.end() } } impl Serialize for GraphQLError { fn serialize(&self, ser: S) -> Result { #[derive(Serialize)] struct Helper { message: &'static str, } match self { Self::ParseError(e) => [e].serialize(ser), Self::ValidationError(es) => es.serialize(ser), Self::NoOperationProvided => [Helper { message: "Must provide an operation", }] .serialize(ser), Self::MultipleOperationsProvided => [Helper { message: "Must provide operation name \ if query contains multiple operations", }] .serialize(ser), Self::UnknownOperationName => [Helper { message: "Unknown operation", }] .serialize(ser), Self::IsSubscription => [Helper { message: "Expected query, got subscription", }] .serialize(ser), Self::NotSubscription => [Helper { message: "Expected subscription, got query", }] .serialize(ser), } } } impl<'de, S: Deserialize<'de>> Deserialize<'de> for InputValue { fn deserialize>(de: D) -> Result { struct Visitor(PhantomData); impl<'de, S: Deserialize<'de>> de::Visitor<'de> for Visitor { type Value = InputValue; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("a valid input value") } fn visit_bool(self, b: bool) -> Result { S::deserialize(b.into_deserializer()).map(InputValue::Scalar) } fn visit_i8(self, n: i8) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_i16(self, n: i16) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_i32(self, n: i32) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_i64(self, n: i64) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } serde_if_integer128! { fn visit_i128(self, n: i128) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } } fn visit_u8(self, n: u8) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_u16(self, n: u16) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_u32(self, n: u32) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_u64(self, n: u64) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } serde_if_integer128! { fn visit_u128(self, n: u128) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } } fn visit_f32(self, n: f32) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_f64(self, n: f64) -> Result { S::deserialize(n.into_deserializer()).map(InputValue::Scalar) } fn visit_char(self, c: char) -> Result { S::deserialize(c.into_deserializer()).map(InputValue::Scalar) } fn visit_str(self, s: &str) -> Result { S::deserialize(s.into_deserializer()).map(InputValue::Scalar) } fn visit_string(self, s: String) -> Result { S::deserialize(s.into_deserializer()).map(InputValue::Scalar) } fn visit_bytes(self, b: &[u8]) -> Result { S::deserialize(b.into_deserializer()).map(InputValue::Scalar) } fn visit_byte_buf(self, b: Vec) -> Result { S::deserialize(b.into_deserializer()).map(InputValue::Scalar) } fn visit_none(self) -> Result { Ok(InputValue::Null) } fn visit_unit(self) -> Result { Ok(InputValue::Null) } fn visit_seq(self, mut visitor: V) -> Result where V: de::SeqAccess<'de>, { let mut vals = Vec::new(); while let Some(v) = visitor.next_element()? { vals.push(v); } Ok(InputValue::list(vals)) } fn visit_map(self, mut visitor: V) -> Result where V: de::MapAccess<'de>, { let mut obj = IndexMap::>::with_capacity( visitor.size_hint().unwrap_or(0), ); while let Some((key, val)) = visitor.next_entry()? { obj.insert(key, val); } Ok(InputValue::object(obj)) } } de.deserialize_any(Visitor(PhantomData)) } } impl Serialize for InputValue { fn serialize(&self, ser: S) -> Result { match self { Self::Null | Self::Variable(_) => ser.serialize_unit(), Self::Scalar(s) => s.serialize(ser), Self::Enum(e) => ser.serialize_str(e), Self::List(l) => l.iter().map(|x| &x.item).collect::>().serialize(ser), Self::Object(o) => o .iter() .map(|(k, v)| (k.item.as_str(), &v.item)) .collect::>() .serialize(ser), } } } impl Serialize for RuleError { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(2))?; map.serialize_key("message")?; map.serialize_value(self.message())?; map.serialize_key("locations")?; map.serialize_value(self.locations())?; map.end() } } impl Serialize for SourcePosition { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(2))?; let line = self.line() + 1; map.serialize_key("line")?; map.serialize_value(&line)?; let column = self.column() + 1; map.serialize_key("column")?; map.serialize_value(&column)?; map.end() } } impl Serialize for Spanning { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(2))?; let msg = self.item.to_string(); map.serialize_key("message")?; map.serialize_value(&msg)?; let mut loc = IndexMap::new(); loc.insert("line".to_owned(), self.start().line() + 1); loc.insert("column".to_owned(), self.start().column() + 1); let locations = vec![loc]; map.serialize_key("locations")?; map.serialize_value(&locations)?; map.end() } } impl Serialize for Object { fn serialize(&self, ser: S) -> Result { let mut map = ser.serialize_map(Some(self.field_count()))?; for (f, v) in self.iter() { map.serialize_key(f)?; map.serialize_value(v)?; } map.end() } } impl Serialize for Value { fn serialize(&self, ser: S) -> Result { match self { Self::Null => ser.serialize_unit(), Self::Scalar(s) => s.serialize(ser), Self::List(l) => l.serialize(ser), Self::Object(o) => o.serialize(ser), } } } impl<'de> Deserialize<'de> for DefaultScalarValue { fn deserialize>(de: D) -> Result { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = DefaultScalarValue; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a valid input value") } fn visit_bool(self, b: bool) -> Result { Ok(DefaultScalarValue::Boolean(b)) } fn visit_i64(self, n: i64) -> Result { if n >= i64::from(i32::MIN) && n <= i64::from(i32::MAX) { Ok(DefaultScalarValue::Int(n.try_into().unwrap())) } else { // Browser's `JSON.stringify()` serializes all numbers // having no fractional part as integers (no decimal point), // so we must parse large integers as floating point, // otherwise we would error on transferring large floating // point numbers. // TODO: Use `FloatToInt` conversion once stabilized: // https://github.com/rust-lang/rust/issues/67057 Ok(DefaultScalarValue::Float(n as f64)) } } fn visit_u64(self, n: u64) -> Result { if n <= u64::try_from(i32::MAX).unwrap() { self.visit_i64(n.try_into().unwrap()) } else { // Browser's `JSON.stringify()` serializes all numbers // having no fractional part as integers (no decimal point), // so we must parse large integers as floating point, // otherwise we would error on transferring large floating // point numbers. // TODO: Use `FloatToInt` conversion once stabilized: // https://github.com/rust-lang/rust/issues/67057 Ok(DefaultScalarValue::Float(n as f64)) } } fn visit_f64(self, f: f64) -> Result { Ok(DefaultScalarValue::Float(f)) } fn visit_str(self, s: &str) -> Result { self.visit_string(s.into()) } fn visit_string(self, s: String) -> Result { Ok(DefaultScalarValue::String(s)) } } de.deserialize_any(Visitor) } } #[cfg(test)] mod tests { use serde_json::{from_str, to_string}; use crate::{ ast::InputValue, graphql_input_value, value::{DefaultScalarValue, Object}, FieldError, Value, }; use super::{ExecutionError, GraphQLError}; #[test] fn int() { assert_eq!( from_str::("1235").unwrap(), graphql_input_value!(1235), ); } #[test] fn float() { assert_eq!( from_str::("2.0").unwrap(), graphql_input_value!(2.0), ); // large value without a decimal part is also float assert_eq!( from_str::("123567890123").unwrap(), graphql_input_value!(123_567_890_123.0), ); } #[test] fn errors() { assert_eq!( to_string(&GraphQLError::UnknownOperationName).unwrap(), r#"[{"message":"Unknown operation"}]"#, ); } #[test] fn error_extensions() { let mut obj: Object = Object::with_capacity(1); obj.add_field("foo", Value::scalar("bar")); assert_eq!( to_string(&ExecutionError::at_origin(FieldError::new( "foo error", Value::Object(obj), ))) .unwrap(), r#"{"message":"foo error","locations":[{"line":1,"column":1}],"path":[],"extensions":{"foo":"bar"}}"#, ); } } juniper-0.16.2/src/integrations/time.rs000064400000000000000000000547261046102023000162050ustar 00000000000000//! GraphQL support for [`time`] crate types. //! //! # Supported types //! //! | Rust type | Format | GraphQL scalar | //! |-----------------------|-----------------------|---------------------| //! | [`Date`] | `yyyy-MM-dd` | [`Date`][s1] | //! | [`Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | //! | [`PrimitiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | //! | [`OffsetDateTime`] | [RFC 3339] string | [`DateTime`][s4] | //! | [`UtcOffset`] | `±hh:mm` | [`UtcOffset`][s5] | //! //! [`Date`]: time::Date //! [`OffsetDateTime`]: time::OffsetDateTime //! [`PrimitiveDateTime`]: time::PrimitiveDateTime //! [`Time`]: time::Time //! [`UtcOffset`]: time::UtcOffset //! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 //! [s1]: https://graphql-scalars.dev/docs/scalars/date //! [s2]: https://graphql-scalars.dev/docs/scalars/local-time //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time //! [s5]: https://graphql-scalars.dev/docs/scalars/utc-offset use time::{ format_description::{well_known::Rfc3339, FormatItem}, macros::format_description, }; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Date in the proleptic Gregorian calendar (without time zone). /// /// Represents a description of the date (as used for birthdays, for example). /// It cannot represent an instant on the time-line. /// /// [`Date` scalar][1] compliant. /// /// See also [`time::Date`][2] for details. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date /// [2]: https://docs.rs/time/*/time/struct.Date.html #[graphql_scalar( with = date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", )] pub type Date = time::Date; mod date { use super::*; /// Format of a [`Date` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date const FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); pub(super) fn to_output(v: &Date) -> Value { Value::scalar( v.format(FORMAT) .unwrap_or_else(|e| panic!("Failed to format `Date`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}"))) } } /// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]` /// format. /// /// All minutes are assumed to have exactly 60 seconds; no attempt is made to /// handle leap seconds (either positive or negative). /// /// [`LocalTime` scalar][1] compliant. /// /// See also [`time::Time`][2] for details. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/time/*/time/struct.Time.html #[graphql_scalar(with = local_time, parse_token(String))] pub type LocalTime = time::Time; mod local_time { use super::*; /// Full format of a [`LocalTime` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT: &[FormatItem<'_>] = format_description!("[hour]:[minute]:[second].[subsecond digits:3]"); /// Format of a [`LocalTime` scalar][1] without milliseconds. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_MILLIS: &[FormatItem<'_>] = format_description!("[hour]:[minute]:[second]"); /// Format of a [`LocalTime` scalar][1] without seconds. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]"); pub(super) fn to_output(v: &LocalTime) -> Value { Value::scalar( if v.millisecond() == 0 { v.format(FORMAT_NO_MILLIS) } else { v.format(FORMAT) } .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing // error to be most informative. LocalTime::parse(s, FORMAT_NO_MILLIS) .or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS)) .or_else(|_| LocalTime::parse(s, FORMAT)) .map_err(|e| format!("Invalid `LocalTime`: {e}")) }) } } /// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format. /// /// See also [`time::PrimitiveDateTime`][2] for details. /// /// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html #[graphql_scalar(with = local_date_time, parse_token(String))] pub type LocalDateTime = time::PrimitiveDateTime; mod local_date_time { use super::*; /// Format of a [`LocalDateTime`] scalar. const FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); pub(super) fn to_output(v: &LocalDateTime) -> Value { Value::scalar( v.format(FORMAT) .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}")) }) } } /// Combined date and time (with time zone) in [RFC 3339][0] format. /// /// Represents a description of an exact instant on the time-line (such as the /// instant that a user account was created). /// /// [`DateTime` scalar][1] compliant. /// /// See also [`time::OffsetDateTime`][2] for details. /// /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html #[graphql_scalar( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", )] pub type DateTime = time::OffsetDateTime; mod date_time { use super::*; pub(super) fn to_output(v: &DateTime) -> Value { Value::scalar( v.to_offset(UtcOffset::UTC) .format(&Rfc3339) .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {e}")) }) .map(|dt| dt.to_offset(UtcOffset::UTC)) } } /// Format of a [`UtcOffset` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = format_description!("[offset_hour sign:mandatory]:[offset_minute]"); /// Offset from UTC in `±hh:mm` format. See [list of database time zones][0]. /// /// [`UtcOffset` scalar][1] compliant. /// /// See also [`time::UtcOffset`][2] for details. /// /// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset /// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html #[graphql_scalar( with = utc_offset, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", )] pub type UtcOffset = time::UtcOffset; mod utc_offset { use super::*; pub(super) fn to_output(v: &UtcOffset) -> Value { Value::scalar( v.format(UTC_OFFSET_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {e}")), ) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { UtcOffset::parse(s, UTC_OFFSET_FORMAT) .map_err(|e| format!("Invalid `UtcOffset`: {e}")) }) } } #[cfg(test)] mod date_test { use time::macros::date; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::Date; #[test] fn parses_correct_input() { for (raw, expected) in [ ("1996-12-19", date!(1996 - 12 - 19)), ("1564-01-30", date!(1564 - 01 - 30)), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = Date::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("1996-13-19"), graphql_input_value!("1564-01-61"), graphql_input_value!("2021-11-31"), graphql_input_value!("11-31"), graphql_input_value!("2021-11"), graphql_input_value!("2021"), graphql_input_value!("31"), graphql_input_value!("i'm not even a date"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = Date::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ (date!(1996 - 12 - 19), graphql_input_value!("1996-12-19")), (date!(1564 - 01 - 30), graphql_input_value!("1564-01-30")), (date!(2020 - W 01 - 3), graphql_input_value!("2020-01-01")), (date!(2020 - 001), graphql_input_value!("2020-01-01")), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod local_time_test { use time::macros::time; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::LocalTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ("14:23:43", time!(14:23:43)), ("14:00:00", time!(14:00)), ("14:00", time!(14:00)), ("14:32", time!(14:32:00)), ("14:00:00.000", time!(14:00)), ("14:23:43.345", time!(14:23:43.345)), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = LocalTime::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("23:78:43"), graphql_input_value!("23:78:"), graphql_input_value!("23:18:99"), graphql_input_value!("23:18:22.4351"), graphql_input_value!("23:18:22."), graphql_input_value!("23:18:22.3"), graphql_input_value!("23:18:22.03"), graphql_input_value!("22.03"), graphql_input_value!("24:00"), graphql_input_value!("24:00:00"), graphql_input_value!("24:00:00.000"), graphql_input_value!("i'm not even a time"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = LocalTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ (time!(1:02:03.004_005), graphql_input_value!("01:02:03.004")), (time!(0:00), graphql_input_value!("00:00:00")), (time!(12:00 pm), graphql_input_value!("12:00:00")), (time!(1:02:03), graphql_input_value!("01:02:03")), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod local_date_time_test { use time::macros::datetime; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::LocalDateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ("1996-12-19 14:23:43", datetime!(1996-12-19 14:23:43)), ("1564-01-30 14:00:00", datetime!(1564-01-30 14:00)), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = LocalDateTime::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("56:34:22.000"), graphql_input_value!("1996-12-1914:23:43"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("1996-12-19 14:23:43Z"), graphql_input_value!("1996-12-19 14:23:43.543"), graphql_input_value!("1996-12-19 14:23"), graphql_input_value!("1996-12-19 14:23:1"), graphql_input_value!("1996-12-19 14:23:"), graphql_input_value!("1996-12-19 23:78:43"), graphql_input_value!("1996-12-19 23:18:99"), graphql_input_value!("1996-12-19 24:00:00"), graphql_input_value!("1996-12-19 99:02:13"), graphql_input_value!("i'm not even a datetime"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = LocalDateTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( datetime!(1996-12-19 12:00 am), graphql_input_value!("1996-12-19 00:00:00"), ), ( datetime!(1564-01-30 14:00), graphql_input_value!("1564-01-30 14:00:00"), ), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod date_time_test { use time::macros::datetime; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::DateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "2014-11-28T21:00:09+09:00", datetime!(2014-11-28 21:00:09 +9), ), ("2014-11-28T21:00:09Z", datetime!(2014-11-28 21:00:09 +0)), ( "2014-11-28T21:00:09+00:00", datetime!(2014-11-28 21:00:09 +0), ), ( "2014-11-28T21:00:09.05+09:00", datetime!(2014-11-28 12:00:09.05 +0), ), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = DateTime::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("56:34:22"), graphql_input_value!("56:34:22.000"), graphql_input_value!("1996-12-1914:23:43"), graphql_input_value!("1996-12-19 14:23:43Z"), graphql_input_value!("1996-12-19T14:23:43"), graphql_input_value!("1996-12-19T14:23:43ZZ"), graphql_input_value!("1996-12-19T14:23:43.543"), graphql_input_value!("1996-12-19T14:23"), graphql_input_value!("1996-12-19T14:23:1"), graphql_input_value!("1996-12-19T14:23:"), graphql_input_value!("1996-12-19T23:78:43Z"), graphql_input_value!("1996-12-19T23:18:99Z"), graphql_input_value!("1996-12-19T24:00:00Z"), graphql_input_value!("1996-12-19T99:02:13Z"), graphql_input_value!("1996-12-19T99:02:13Z"), graphql_input_value!("1996-12-19T12:02:13+4444444"), graphql_input_value!("i'm not even a datetime"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = DateTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ ( datetime!(1996-12-19 12:00 am UTC), graphql_input_value!("1996-12-19T00:00:00Z"), ), ( datetime!(1564-01-30 14:00 +9), graphql_input_value!("1564-01-30T05:00:00Z"), ), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod utc_offset_test { use time::macros::offset; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; use super::UtcOffset; #[test] fn parses_correct_input() { for (raw, expected) in [ ("+00:00", offset!(+0)), ("-00:00", offset!(-0)), ("+10:00", offset!(+10)), ("-07:30", offset!(-7:30)), ("+14:00", offset!(+14)), ("-12:00", offset!(-12)), ] { let input: InputValue = graphql_input_value!((raw)); let parsed = UtcOffset::from_input_value(&input); assert!( parsed.is_ok(), "failed to parse `{raw}`: {:?}", parsed.unwrap_err(), ); assert_eq!(parsed.unwrap(), expected, "input: {raw}"); } } #[test] fn fails_on_invalid_input() { for input in [ graphql_input_value!("12"), graphql_input_value!("12:"), graphql_input_value!("12:00"), graphql_input_value!("+12:"), graphql_input_value!("+12:0"), graphql_input_value!("+12:00:34"), graphql_input_value!("+12"), graphql_input_value!("-12"), graphql_input_value!("-12:"), graphql_input_value!("-12:0"), graphql_input_value!("-12:00:32"), graphql_input_value!("-999:00"), graphql_input_value!("+999:00"), graphql_input_value!("i'm not even an offset"), graphql_input_value!(2.32), graphql_input_value!(1), graphql_input_value!(null), graphql_input_value!(false), ] { let input: InputValue = input; let parsed = UtcOffset::from_input_value(&input); assert!(parsed.is_err(), "allows input: {input:?}"); } } #[test] fn formats_correctly() { for (val, expected) in [ (offset!(+1), graphql_input_value!("+01:00")), (offset!(+0), graphql_input_value!("+00:00")), (offset!(-2:30), graphql_input_value!("-02:30")), ] { let actual: InputValue = val.to_input_value(); assert_eq!(actual, expected, "on value: {val}"); } } } #[cfg(test)] mod integration_test { use time::macros::{date, datetime, offset, time}; use crate::{ execute, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; use super::{Date, DateTime, LocalDateTime, LocalTime, UtcOffset}; #[tokio::test] async fn serializes() { struct Root; #[graphql_object] impl Root { fn date() -> Date { date!(2015 - 03 - 14) } fn local_time() -> LocalTime { time!(16:07:08) } fn local_date_time() -> LocalDateTime { datetime!(2016-07-08 09:10:11) } fn date_time() -> DateTime { datetime!(1996-12-19 16:39:57 -8) } fn utc_offset() -> UtcOffset { offset!(+11:30) } } const DOC: &str = r#"{ date localTime localDateTime dateTime, utcOffset, }"#; let schema = RootNode::new( Root, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({ "date": "2015-03-14", "localTime": "16:07:08", "localDateTime": "2016-07-08 09:10:11", "dateTime": "1996-12-20T00:39:57Z", "utcOffset": "+11:30", }), vec![], )), ); } } juniper-0.16.2/src/integrations/url.rs000064400000000000000000000020471046102023000160360ustar 00000000000000//! GraphQL support for [url](https://github.com/servo/rust-url) types. use crate::{graphql_scalar, InputValue, ScalarValue, Value}; #[graphql_scalar(with = url_scalar, parse_token(String))] type Url = url::Url; mod url_scalar { use super::*; pub(super) fn to_output(v: &Url) -> Value { Value::scalar(v.as_str().to_owned()) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {e}"))) } } #[cfg(test)] mod test { use url::Url; use crate::{graphql_input_value, InputValue}; #[test] fn url_from_input() { let raw = "https://example.net/"; let input: InputValue = graphql_input_value!((raw)); let parsed: Url = crate::FromInputValue::from_input_value(&input).unwrap(); let url = Url::parse(raw).unwrap(); assert_eq!(parsed, url); } } juniper-0.16.2/src/integrations/uuid.rs000064400000000000000000000022071046102023000162000ustar 00000000000000//! GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types. #![allow(clippy::needless_lifetimes)] use crate::{graphql_scalar, InputValue, ScalarValue, Value}; #[graphql_scalar(with = uuid_scalar, parse_token(String))] type Uuid = uuid::Uuid; mod uuid_scalar { use super::*; pub(super) fn to_output(v: &Uuid) -> Value { Value::scalar(v.to_string()) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {e}"))) } } #[cfg(test)] mod test { use uuid::Uuid; use crate::{graphql_input_value, FromInputValue, InputValue}; #[test] fn uuid_from_input() { let raw = "123e4567-e89b-12d3-a456-426655440000"; let input: InputValue = graphql_input_value!((raw)); let parsed: Uuid = FromInputValue::from_input_value(&input).unwrap(); let id = Uuid::parse_str(raw).unwrap(); assert_eq!(parsed, id); } } juniper-0.16.2/src/introspection/mod.rs000064400000000000000000000014261046102023000162050ustar 00000000000000/// From pub(crate) const INTROSPECTION_QUERY: &str = include_str!("./query.graphql"); pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str = include_str!("./query_without_descriptions.graphql"); /// The desired GraphQL introspection format for the canonical query /// () #[derive(Clone, Copy, Debug, Default)] pub enum IntrospectionFormat { /// The canonical GraphQL introspection query. #[default] All, /// The canonical GraphQL introspection query without descriptions. WithoutDescriptions, } juniper-0.16.2/src/introspection/query.graphql000064400000000000000000000025121046102023000176020ustar 00000000000000query IntrospectionQuery { __schema { description queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description isRepeatable locations args { ...InputValue } } } } fragment FullType on __Type { kind name description specifiedByUrl fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } juniper-0.16.2/src/introspection/query_without_descriptions.graphql000064400000000000000000000023541046102023000241570ustar 00000000000000query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name isRepeatable locations args { ...InputValue } } } } fragment FullType on __Type { kind name specifiedByUrl fields(includeDeprecated: true) { name args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } juniper-0.16.2/src/lib.rs000064400000000000000000000246531046102023000133030ustar 00000000000000#![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_cfg))] // Due to `schema_introspection` test. #![cfg_attr(test, recursion_limit = "256")] #![warn(missing_docs)] // Required for using `juniper_codegen` macros inside this crate to resolve // absolute `::juniper` path correctly, without errors. extern crate core; extern crate self as juniper; use std::fmt; // These are required by the code generated via the `juniper_codegen` macros. #[doc(hidden)] pub use {async_trait::async_trait, futures, serde, static_assertions as sa}; #[doc(inline)] pub use futures::future::{BoxFuture, LocalBoxFuture}; // Depend on juniper_codegen and re-export everything in it. // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, GraphQLUnion, }; #[doc(hidden)] #[macro_use] pub mod macros; mod ast; pub mod executor; mod introspection; pub mod parser; pub(crate) mod schema; mod types; mod util; pub mod validation; mod value; // This needs to be public until docs have support for private modules: // https://github.com/rust-lang/cargo/issues/1520 pub mod http; pub mod integrations; #[cfg(all(test, not(feature = "expose-test-schema")))] mod tests; #[cfg(feature = "expose-test-schema")] pub mod tests; #[cfg(test)] mod executor_tests; // Needs to be public because macros use it. pub use crate::util::to_camel_case; use crate::{ executor::{execute_validated_query, get_operation}, introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS}, parser::parse_document_source, validation::{ rules, validate_input_values, visit as visit_rule, visit_all_rules, MultiVisitorNil, ValidatorContext, }, }; pub use crate::{ ast::{ Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection, ToInputValue, Type, }, executor::{ Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadChildren, LookAheadList, LookAheadObject, LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, }, introspection::IntrospectionFormat, macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult}, parser::{ParseError, ScalarToken, Span, Spanning}, schema::{ meta, model::{RootNode, SchemaType}, }, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion}, nullable::Nullable, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue, SubscriptionConnection, SubscriptionCoordinator, }, }, validation::RuleError, value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, }; /// An error that prevented query execution #[allow(missing_docs)] #[derive(Clone, Debug, Eq, PartialEq)] pub enum GraphQLError { ParseError(Spanning), ValidationError(Vec), NoOperationProvided, MultipleOperationsProvided, UnknownOperationName, IsSubscription, NotSubscription, } impl fmt::Display for GraphQLError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ParseError(e) => write!(f, "{e}"), Self::ValidationError(errs) => { for e in errs { writeln!(f, "{e}")?; } Ok(()) } Self::NoOperationProvided => write!(f, "No operation provided"), Self::MultipleOperationsProvided => write!(f, "Multiple operations provided"), Self::UnknownOperationName => write!(f, "Unknown operation name"), Self::IsSubscription => write!(f, "Operation is a subscription"), Self::NotSubscription => write!(f, "Operation is not a subscription"), } } } impl std::error::Error for GraphQLError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::ParseError(e) => Some(e), Self::ValidationError(errs) => Some(errs.first()?), Self::NoOperationProvided | Self::MultipleOperationsProvided | Self::UnknownOperationName | Self::IsSubscription | Self::NotSubscription => None, } } } /// Execute a query synchronously in a provided schema pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>( document_source: &'a str, operation_name: Option<&str>, root_node: &'a RootNode, variables: &Variables, context: &QueryT::Context, ) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { let document = parse_document_source(document_source, &root_node.schema)?; { let mut ctx = ValidatorContext::new(&root_node.schema, &document); visit_all_rules(&mut ctx, &document); if root_node.introspection_disabled { visit_rule( &mut MultiVisitorNil.with(rules::disable_introspection::factory()), &mut ctx, &document, ); } let errors = ctx.into_errors(); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } let operation = get_operation(&document, operation_name)?; { let errors = validate_input_values(variables, operation, &root_node.schema); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } execute_validated_query(&document, operation, root_node, variables, context) } /// Execute a query in a provided schema pub async fn execute<'a, S, QueryT, MutationT, SubscriptionT>( document_source: &'a str, operation_name: Option<&str>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &QueryT::Context, ) -> Result<(Value, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLType + Sync, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { let document = parse_document_source(document_source, &root_node.schema)?; { let mut ctx = ValidatorContext::new(&root_node.schema, &document); visit_all_rules(&mut ctx, &document); if root_node.introspection_disabled { visit_rule( &mut MultiVisitorNil.with(rules::disable_introspection::factory()), &mut ctx, &document, ); } let errors = ctx.into_errors(); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } let operation = get_operation(&document, operation_name)?; { let errors = validate_input_values(variables, operation, &root_node.schema); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } executor::execute_validated_query_async(&document, operation, root_node, variables, context) .await } /// Resolve subscription into `ValuesStream` pub async fn resolve_into_stream<'a, S, QueryT, MutationT, SubscriptionT>( document_source: &'a str, operation_name: Option<&str>, root_node: &'a RootNode<'a, QueryT, MutationT, SubscriptionT, S>, variables: &Variables, context: &'a QueryT::Context, ) -> Result<(Value>, Vec>), GraphQLError> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLSubscriptionType, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { let document: crate::ast::OwnedDocument<'a, S> = parse_document_source(document_source, &root_node.schema)?; { let mut ctx = ValidatorContext::new(&root_node.schema, &document); visit_all_rules(&mut ctx, &document); if root_node.introspection_disabled { visit_rule( &mut MultiVisitorNil.with(rules::disable_introspection::factory()), &mut ctx, &document, ); } let errors = ctx.into_errors(); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } let operation = get_operation(&document, operation_name)?; { let errors = validate_input_values(variables, operation, &root_node.schema); if !errors.is_empty() { return Err(GraphQLError::ValidationError(errors)); } } executor::resolve_validated_subscription(&document, operation, root_node, variables, context) .await } /// Execute the reference introspection query in the provided schema pub fn introspect( root_node: &RootNode, context: &QueryT::Context, format: IntrospectionFormat, ) -> Result<(Value, Vec>), GraphQLError> where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { execute_sync( match format { IntrospectionFormat::All => INTROSPECTION_QUERY, IntrospectionFormat::WithoutDescriptions => INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS, }, None, root_node, &Variables::new(), context, ) } impl From> for GraphQLError { fn from(err: Spanning) -> Self { Self::ParseError(err) } } juniper-0.16.2/src/macros/graphql_input_value.rs000064400000000000000000000461151046102023000200670ustar 00000000000000//! [`graphql_input_value!`] macro implementation. //! //! [`graphql_input_value!`]: graphql_input_value /// Constructs [`InputValue`]s via JSON-like syntax. /// /// # Differences from [`graphql_value!`] /// /// - [`InputValue::Enum`] is constructed with `ident`, so to capture outer /// variable as [`InputValue::Scalar`] surround it with parens: `(var)`. /// ```rust /// # use juniper::{graphql_input_value, graphql_value}; /// # /// # type InputValue = juniper::InputValue; /// # type Value = juniper::Value; /// # /// const OUTER_VAR: i32 = 42; /// assert_eq!(graphql_value!(OUTER_VAR), Value::scalar(42)); /// assert_eq!(graphql_input_value!(OUTER_VAR), InputValue::enum_value("OUTER_VAR")); /// assert_eq!(graphql_input_value!((OUTER_VAR)), InputValue::scalar(42)); /// ``` /// /// - [`InputValue::Variable`] is constructed by prefixing `ident` with `@`. /// ```rust /// # use juniper::graphql_input_value; /// # /// # type InputValue = juniper::InputValue; /// # /// assert_eq!(graphql_input_value!(@var), InputValue::variable("var")); /// ``` /// /// - [`InputValue::Object`] key should implement [`Into`]`<`[`String`]`>`. /// ```rust /// # use std::borrow::Cow; /// # /// # use juniper::{graphql_input_value, InputValue}; /// # /// let code = 200; /// let features = vec!["key", "value"]; /// let key: Cow<'static, str> = "key".into(); /// /// let value: InputValue = graphql_input_value!({ /// "code": code, /// "success": code == 200, /// "payload": { /// features[0]: features[1], /// key: @var, /// }, /// }); /// ``` /// /// > __NOTE:__ [`InputValue::List`]s and [`InputValue::Object`]s will be /// > created in a [`Spanning::unlocated`]. /// /// # Example /// /// ```rust /// # use juniper::{graphql_input_value, InputValue}; /// # /// # type V = InputValue; /// # /// # let _: V = /// graphql_input_value!(null); /// # let _: V = /// graphql_input_value!(1234); /// # let _: V = /// graphql_input_value!("test"); /// # let _: V = /// graphql_input_value!([1234, "test", true]); /// # let _: V = /// graphql_input_value!({"key": "value", "foo": 1234}); /// # let _: V = /// graphql_input_value!({"key": ENUM}); /// let captured_var = 42; /// # let _: V = /// graphql_input_value!({"key": (captured_var)}); /// # let _: V = /// graphql_input_value!({"key": @variable}); /// ``` /// /// [`InputValue`]: crate::InputValue /// [`InputValue::Enum`]: crate::InputValue::Enum /// [`InputValue::List`]: crate::InputValue::List /// [`InputValue::Object`]: crate::InputValue::Object /// [`InputValue::Scalar`]: crate::InputValue::Scalar /// [`InputValue::Variable`]: crate::InputValue::Variable /// [`Spanning::unlocated`]: crate::Spanning::unlocated #[macro_export] macro_rules! graphql_input_value { /////////// // Array // /////////// // Done with trailing comma. (@@array [$($elems:expr,)*]) => { $crate::InputValue::list(vec![ $( $elems, )* ]) }; // Done without trailing comma. (@@array [$($elems:expr),*]) => { $crate::InputValue::list(vec![ $( $elems, )* ]) }; // Next element is `null`. (@@array [$($elems:expr,)*] null $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!(null)] $($rest)* ) }; // Next element is `None`. (@@array [$($elems:expr,)*] None $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!(None)] $($rest)* ) }; // Next element is a variable. (@@array [$($elems:expr,)*] @$var:ident $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!(@$var)] $($rest)* ) }; // Next element is an array. (@@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!([$($array)*])] $($rest)* ) }; // Next element is a map. (@@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!({$($map)*})] $($rest)* ) }; // Next element is `true`, `false` or enum ident followed by comma. (@@array [$($elems:expr,)*] $ident:ident, $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!($ident),] $($rest)* ) }; // Next element is `true`, `false` or enum ident without trailing comma. (@@array [$($elems:expr,)*] $last:ident ) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!($last)] ) }; // Next element is an expression followed by comma. (@@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!($next),] $($rest)* ) }; // Last element is an expression with no trailing comma. (@@array [$($elems:expr,)*] $last:expr) => { $crate::graphql_input_value!( @@array [$($elems,)* $crate::graphql_input_value!($last)] ) }; // Comma after the most recent element. (@@array [$($elems:expr),*] , $($rest:tt)*) => { $crate::graphql_input_value!(@@array [$($elems,)*] $($rest)*) }; // Unexpected token after most recent element. (@@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { $crate::graphql_input_value!(@unexpected $unexpected) }; //////////// // Object // //////////// // Done. (@@object $object:ident () () ()) => {}; // Insert the current entry followed by trailing comma. (@@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { $object.push(( $crate::Spanning::unlocated(($($key)+).into()), $crate::Spanning::unlocated($value), )); $crate::graphql_input_value!(@@object $object () ($($rest)*) ($($rest)*)); }; // Current entry followed by unexpected token. (@@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { $crate::graphql_input_value!(@unexpected $unexpected); }; // Insert the last entry without trailing comma. (@@object $object:ident [$($key:tt)+] ($value:expr)) => { $object.push(( $crate::Spanning::unlocated(($($key)+).into()), $crate::Spanning::unlocated($value), )); }; // Next value is `null`. (@@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!(null)) $($rest)* ); }; // Next value is `None`. (@@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!(None)) $($rest)* ); }; // Next value is a variable. (@@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!(@$var)) $($rest)* ); }; // Next value is an array. (@@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!([$($array)*])) $($rest)* ); }; // Next value is a map. (@@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!({$($map)*})) $($rest)* ); }; // Next value is `true`, `false` or enum ident followed by comma. (@@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!($ident)) , $($rest)* ); }; // Next value is `true`, `false` or enum ident without trailing comma. (@@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!($last)) ); }; // Next value is an expression followed by comma. (@@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!($value)) , $($rest)* ); }; // Last value is an expression with no trailing comma. (@@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { $crate::graphql_input_value!( @@object $object [$($key)+] ($crate::graphql_input_value!($value)) ); }; // Missing value for last entry. Trigger a reasonable error message. (@@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_input_value!(); }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_input_value!(); }; // Misplaced colon. Trigger a reasonable error message. (@@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". $crate::graphql_input_value!(@unexpected $colon); }; // Found a comma inside a key. Trigger a reasonable error message. (@@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". $crate::graphql_input_value!(@unexpected $comma); }; // Key is fully parenthesized. This avoids `clippy::double_parens` false // positives because the parenthesization may be necessary here. (@@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object ($key) (: $($rest)*) (: $($rest)*) ); }; // Refuse to absorb colon token into key expression. (@@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { $crate::graphql_input_value!(@@unexpected $($unexpected)+); }; // Munch a token into the current key. (@@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { $crate::graphql_input_value!( @@object $object ($($key)* $tt) ($($rest)*) ($($rest)*) ); }; //////////// // Errors // //////////// (@@unexpected) => {}; ////////////// // Defaults // ////////////// ([ $($arr:tt)* ]$(,)?) => { $crate::graphql_input_value!(@@array [] $($arr)*) }; ({}$(,)?) => { $crate::InputValue::parsed_object(vec![]) }; ({ $($map:tt)+ }$(,)?) => { $crate::InputValue::parsed_object({ let mut object = vec![]; $crate::graphql_input_value!(@@object object () ($($map)*) ($($map)*)); object }) }; (null$(,)?) => ($crate::InputValue::null()); (None$(,)?) => ($crate::InputValue::null()); (true$(,)?) => ($crate::InputValue::from(true)); (false$(,)?) => ($crate::InputValue::from(false)); (@$var:ident$(,)?) => ($crate::InputValue::variable(stringify!($var))); ($enum:ident$(,)?) => ($crate::InputValue::enum_value(stringify!($enum))); (($e:expr)$(,)?) => ($crate::InputValue::from($e)); ($e:expr$(,)?) => ($crate::InputValue::from($e)); } #[cfg(test)] mod tests { use indexmap::{indexmap, IndexMap}; type V = crate::InputValue; #[test] fn null() { assert_eq!(graphql_input_value!(null), V::Null); } #[test] fn scalar() { let val = 42; assert_eq!(graphql_input_value!(1), V::scalar(1)); assert_eq!(graphql_input_value!("val"), V::scalar("val")); assert_eq!(graphql_input_value!(1.34), V::scalar(1.34)); assert_eq!(graphql_input_value!(false), V::scalar(false)); assert_eq!(graphql_input_value!(1 + 2), V::scalar(3)); assert_eq!(graphql_input_value!((val)), V::scalar(42)); } #[test] fn r#enum() { assert_eq!(graphql_input_value!(ENUM), V::enum_value("ENUM")); assert_eq!(graphql_input_value!(lowercase), V::enum_value("lowercase")); } #[test] fn variable() { assert_eq!(graphql_input_value!(@var), V::variable("var")); assert_eq!(graphql_input_value!(@array), V::variable("array")); assert_eq!(graphql_input_value!(@object), V::variable("object")); } #[test] fn list() { let val = 42; assert_eq!(graphql_input_value!([]), V::list(vec![])); assert_eq!(graphql_input_value!([null]), V::list(vec![V::Null])); assert_eq!(graphql_input_value!([1]), V::list(vec![V::scalar(1)])); assert_eq!(graphql_input_value!([1 + 2]), V::list(vec![V::scalar(3)])); assert_eq!(graphql_input_value!([(val)]), V::list(vec![V::scalar(42)])); assert_eq!( graphql_input_value!([ENUM]), V::list(vec![V::enum_value("ENUM")]), ); assert_eq!( graphql_input_value!([lowercase]), V::list(vec![V::enum_value("lowercase")]), ); assert_eq!( graphql_input_value!([@var]), V::list(vec![V::variable("var")]), ); assert_eq!( graphql_input_value!([@array]), V::list(vec![V::variable("array")]), ); assert_eq!( graphql_input_value!([@object]), V::list(vec![V::variable("object")]), ); assert_eq!( graphql_input_value!([1, [2], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(2)]), V::scalar(3), ]), ); assert_eq!( graphql_input_value!([1, [2 + 3], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(5)]), V::scalar(3), ]), ); assert_eq!( graphql_input_value!([1, [ENUM], (val)]), V::list(vec![ V::scalar(1), V::list(vec![V::enum_value("ENUM")]), V::scalar(42), ]), ); assert_eq!( graphql_input_value!([1 + 2, [(val)], @val]), V::list(vec![ V::scalar(3), V::list(vec![V::scalar(42)]), V::variable("val"), ]), ); assert_eq!( graphql_input_value!([1, [@val], ENUM]), V::list(vec![ V::scalar(1), V::list(vec![V::variable("val")]), V::enum_value("ENUM"), ]), ); } #[test] fn object() { let val = 42; assert_eq!( graphql_input_value!({}), V::object(IndexMap::::new()), ); assert_eq!( graphql_input_value!({ "key": null }), V::object(indexmap! {"key" => V::Null}), ); assert_eq!( graphql_input_value!({"key": 123}), V::object(indexmap! {"key" => V::scalar(123)}), ); assert_eq!( graphql_input_value!({"key": 1 + 2}), V::object(indexmap! {"key" => V::scalar(3)}), ); assert_eq!( graphql_input_value!({ "key": (val) }), V::object(indexmap! {"key" => V::scalar(42)}), ); assert_eq!( graphql_input_value!({"key": []}), V::object(indexmap! {"key" => V::list(vec![])}), ); assert_eq!( graphql_input_value!({ "key": [null] }), V::object(indexmap! {"key" => V::list(vec![V::Null])}), ); assert_eq!( graphql_input_value!({"key": [1] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(1)])}), ); assert_eq!( graphql_input_value!({"key": [1 + 2] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(3)])}), ); assert_eq!( graphql_input_value!({ "key": [(val)] }), V::object(indexmap! {"key" => V::list(vec![V::scalar(42)])}), ); assert_eq!( graphql_input_value!({ "key": ENUM }), V::object(indexmap! {"key" => V::enum_value("ENUM")}), ); assert_eq!( graphql_input_value!({ "key": lowercase }), V::object(indexmap! {"key" => V::enum_value("lowercase")}), ); assert_eq!( graphql_input_value!({"key": @val}), V::object(indexmap! {"key" => V::variable("val")}), ); assert_eq!( graphql_input_value!({"key": @array }), V::object(indexmap! {"key" => V::variable("array")}), ); assert_eq!( graphql_input_value!({ "inner": { "key1": (val), "key2": "val", "key3": [{ "inner": 42, }, { "inner": ENUM, "even-more": { "var": @var, }, }], "key4": [1, ["val", 1 + 3], null, @array], }, "more": @var, }), V::object(indexmap! { "inner" => V::object(indexmap! { "key1" => V::scalar(42), "key2" => V::scalar("val"), "key3" => V::list(vec![ V::object(indexmap! { "inner" => V::scalar(42), }), V::object(indexmap! { "inner" => V::enum_value("ENUM"), "even-more" => V::object(indexmap! { "var" => V::variable("var"), }), }), ]), "key4" => V::list(vec![ V::scalar(1), V::list(vec![ V::scalar("val"), V::scalar(4), ]), V::Null, V::variable("array"), ]), }), "more" => V::variable("var"), }), ); } #[test] fn option() { let val = Some(42); assert_eq!(graphql_input_value!(None), V::Null); assert_eq!(graphql_input_value!(Some(42)), V::scalar(42)); assert_eq!(graphql_input_value!((val)), V::scalar(42)); } } juniper-0.16.2/src/macros/graphql_value.rs000064400000000000000000000263361046102023000166530ustar 00000000000000//! [`graphql_value!`] macro implementation. //! //! [`graphql_value!`]: graphql_value /// Constructs [`Value`]s via JSON-like syntax. /// /// [`Value`] objects are used mostly when creating custom errors from fields. /// /// [`Value::Object`] key should implement [`AsRef`]`<`[`str`]`>`. /// ```rust /// # use juniper::{graphql_value, Value}; /// # /// let code = 200; /// let features = ["key", "value"]; /// /// let value: Value = graphql_value!({ /// "code": code, /// "success": code == 200, /// "payload": { /// features[0]: features[1], /// }, /// }); /// ``` /// /// # Example /// /// Resulting JSON will look just like what you passed in. /// ```rust /// # use juniper::{graphql_value, DefaultScalarValue, Value}; /// # /// # type V = Value; /// # /// # let _: V = /// graphql_value!(null); /// # let _: V = /// graphql_value!(1234); /// # let _: V = /// graphql_value!("test"); /// # let _: V = /// graphql_value!([1234, "test", true]); /// # let _: V = /// graphql_value!({"key": "value", "foo": 1234}); /// ``` /// /// [`Value`]: crate::Value /// [`Value::Object`]: crate::Value::Object #[macro_export] macro_rules! graphql_value { /////////// // Array // /////////// // Done with trailing comma. (@array [$($elems:expr,)*]) => { $crate::Value::list(vec![ $( $elems, )* ]) }; // Done without trailing comma. (@array [$($elems:expr),*]) => { $crate::Value::list(vec![ $( $crate::graphql_value!($elems), )* ]) }; // Next element is `null`. (@array [$($elems:expr,)*] null $($rest:tt)*) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!(null)] $($rest)* ) }; // Next element is `None`. (@array [$($elems:expr,)*] None $($rest:tt)*) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!(None)] $($rest)* ) }; // Next element is an array. (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!([$($array)*])] $($rest)* ) }; // Next element is a map. (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!({$($map)*})] $($rest)* ) }; // Next element is an expression followed by comma. (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!($next),] $($rest)* ) }; // Last element is an expression with no trailing comma. (@array [$($elems:expr,)*] $last:expr) => { $crate::graphql_value!( @array [$($elems,)* $crate::graphql_value!($last)] ) }; // Comma after the most recent element. (@array [$($elems:expr),*] , $($rest:tt)*) => { $crate::graphql_value!(@array [$($elems,)*] $($rest)*) }; // Unexpected token after most recent element. (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { $crate::graphql_value!(@unexpected $unexpected) }; //////////// // Object // //////////// // Done. (@object $object:ident () () ()) => {}; // Insert the current entry followed by trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { let _ = $object.add_field(($($key)+), $value); $crate::graphql_value!(@object $object () ($($rest)*) ($($rest)*)); }; // Current entry followed by unexpected token. (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { $crate::graphql_value!(@unexpected $unexpected); }; // Insert the last entry without trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr)) => { let _ = $object.add_field(($($key)+), $value); }; // Next value is `null`. (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!(null)) $($rest)* ); }; // Next value is `None`. (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!(None)) $($rest)* ); }; // Next value is an array. (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!([$($array)*])) $($rest)* ); }; // Next value is a map. (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!({$($map)*})) $($rest)* ); }; // Next value is an expression followed by comma. (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!($value)) , $($rest)* ); }; // Last value is an expression with no trailing comma. (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { $crate::graphql_value!( @object $object [$($key)+] ($crate::graphql_value!($value)) ); }; // Missing value for last entry. Trigger a reasonable error message. (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_value!(); }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_value!(); }; // Misplaced colon. Trigger a reasonable error message. (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". $crate::graphql_value!(@unexpected $colon); }; // Found a comma inside a key. Trigger a reasonable error message. (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". $crate::graphql_value!(@unexpected $comma); }; // Key is fully parenthesized. This avoids `clippy::double_parens` false // positives because the parenthesization may be necessary here. (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { $crate::graphql_value!(@object $object ($key) (: $($rest)*) (: $($rest)*)); }; // Refuse to absorb colon token into key expression. (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { $crate::graphql_value!(@unexpected $($unexpected)+); }; // Munch a token into the current key. (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { $crate::graphql_value!( @object $object ($($key)* $tt) ($($rest)*) ($($rest)*) ); }; //////////// // Errors // //////////// (@unexpected) => {}; ////////////// // Defaults // ////////////// ([ $($arr:tt)* ]$(,)?) => { $crate::graphql_value!(@array [] $($arr)*) }; ({}$(,)?) => { $crate::Value::object($crate::Object::with_capacity(0)) }; ({ $($map:tt)+ }$(,)?) => { $crate::Value::object({ let mut object = $crate::Object::with_capacity(0); $crate::graphql_value!(@object object () ($($map)*) ($($map)*)); object }) }; (null$(,)?) => ($crate::Value::null()); (None$(,)?) => ($crate::Value::null()); ($e:expr$(,)?) => ($crate::Value::from($e)); } #[cfg(test)] mod tests { type V = crate::Value; #[test] fn null() { assert_eq!(graphql_value!(null), V::Null); } #[test] fn scalar() { let val = 42; assert_eq!(graphql_value!(1), V::scalar(1)); assert_eq!(graphql_value!("val"), V::scalar("val")); assert_eq!(graphql_value!(1.34), V::scalar(1.34)); assert_eq!(graphql_value!(false), V::scalar(false)); assert_eq!(graphql_value!(1 + 2), V::scalar(3)); assert_eq!(graphql_value!(val), V::scalar(42)); } #[test] fn list() { let val = 42; assert_eq!(graphql_value!([]), V::list(vec![])); assert_eq!(graphql_value!([null]), V::list(vec![V::Null])); assert_eq!(graphql_value!([1]), V::list(vec![V::scalar(1)])); assert_eq!(graphql_value!([1 + 2]), V::list(vec![V::scalar(3)])); assert_eq!(graphql_value!([val]), V::list(vec![V::scalar(42)])); assert_eq!( graphql_value!([1, [2], 3]), V::list(vec![ V::scalar(1), V::list(vec![V::scalar(2)]), V::scalar(3), ]), ); assert_eq!( graphql_value!(["string", [2 + 3], true]), V::list(vec![ V::scalar("string"), V::list(vec![V::scalar(5)]), V::scalar(true), ]), ); } #[test] fn object() { let val = 42; assert_eq!( graphql_value!({}), V::object(Vec::<(String, _)>::new().into_iter().collect()), ); assert_eq!( graphql_value!({ "key": null }), V::object(vec![("key", V::Null)].into_iter().collect()), ); assert_eq!( graphql_value!({ "key": 123 }), V::object(vec![("key", V::scalar(123))].into_iter().collect()), ); assert_eq!( graphql_value!({ "key": 1 + 2 }), V::object(vec![("key", V::scalar(3))].into_iter().collect()), ); assert_eq!( graphql_value!({ "key": [] }), V::object(vec![("key", V::list(vec![]))].into_iter().collect()), ); assert_eq!( graphql_value!({ "key": [null] }), V::object(vec![("key", V::list(vec![V::Null]))].into_iter().collect()), ); assert_eq!( graphql_value!({ "key": [1] }), V::object( vec![("key", V::list(vec![V::scalar(1)]))] .into_iter() .collect(), ), ); assert_eq!( graphql_value!({ "key": [1 + 2] }), V::object( vec![("key", V::list(vec![V::scalar(3)]))] .into_iter() .collect(), ), ); assert_eq!( graphql_value!({ "key": [val] }), V::object( vec![("key", V::list(vec![V::scalar(42)]))] .into_iter() .collect(), ), ); } #[test] fn option() { let val = Some(42); assert_eq!(graphql_value!(None), V::Null); assert_eq!(graphql_value!(Some(42)), V::scalar(42)); assert_eq!(graphql_value!(val), V::scalar(42)); } } juniper-0.16.2/src/macros/graphql_vars.rs000064400000000000000000000436251046102023000165120ustar 00000000000000//! [`graphql_vars!`] macro implementation. //! //! [`graphql_vars!`]: graphql_vars /// Constructs [`Variables`] via JSON-like syntax. /// /// [`Variables`] key should implement [`Into`]`<`[`String`]`>`. /// ```rust /// # use std::borrow::Cow; /// # /// # use juniper::{graphql_vars, Variables}; /// # /// let code = 200; /// let features = vec!["key", "value"]; /// let key: Cow<'static, str> = "key".into(); /// /// let value: Variables = graphql_vars! { /// "code": code, /// "success": code == 200, /// features[0]: features[1], /// key: @var, /// }; /// ``` /// /// See [`graphql_input_value!`] for more info on syntax of value after `:`. /// /// [`graphql_input_value!`]: crate::graphql_input_value /// [`Variables`]: crate::Variables #[macro_export] macro_rules! graphql_vars { //////////// // Object // //////////// // Done. (@object $object:ident () () ()) => {}; // Insert the current entry followed by trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { let _ = $object.insert(($($key)+).into(), $value); $crate::graphql_vars! {@object $object () ($($rest)*) ($($rest)*)}; }; // Current entry followed by unexpected token. (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { $crate::graphql_vars! {@unexpected $unexpected}; }; // Insert the last entry without trailing comma. (@object $object:ident [$($key:tt)+] ($value:expr)) => { let _ = $object.insert(($($key)+).into(), $value); }; // Next value is `null`. (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!(null)) $($rest)* }; }; // Next value is `None`. (@object $object:ident ($($key:tt)+) (: None $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!(None)) $($rest)* }; }; // Next value is a variable. (@object $object:ident ($($key:tt)+) (: @$var:ident $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!(@$var)) $($rest)* }; }; // Next value is an array. (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!([$($array)*])) $($rest)* }; }; // Next value is a map. (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!({$($map)*})) $($rest)* }; }; // Next value is `true`, `false` or enum ident followed by a comma. (@object $object:ident ($($key:tt)+) (: $ident:ident , $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!($ident)) , $($rest)* }; }; // Next value is `true`, `false` or enum ident without trailing comma. (@object $object:ident ($($key:tt)+) (: $last:ident ) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!($last)) }; }; // Next value is an expression followed by comma. (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!($value)) , $($rest)* }; }; // Last value is an expression with no trailing comma. (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { $crate::graphql_vars! { @object $object [$($key)+] ($crate::graphql_input_value!($value)) }; }; // Missing value for last entry. Trigger a reasonable error message. (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_vars! {}; }; // Missing colon and value for last entry. Trigger a reasonable error // message. (@object $object:ident ($($key:tt)+) () $copy:tt) => { // "unexpected end of macro invocation" $crate::graphql_vars! {}; }; // Misplaced colon. Trigger a reasonable error message. (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `:`". $crate::graphql_vars! {@unexpected $colon}; }; // Found a comma inside a key. Trigger a reasonable error message. (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { // Takes no arguments so "no rules expected the token `,`". $crate::graphql_vars! {@unexpected $comma}; }; // Key is fully parenthesized. This avoids clippy double_parens false // positives because the parenthesization may be necessary here. (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object ($key) (: $($rest)*) (: $($rest)*) }; }; // Refuse to absorb colon token into key expression. (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { $crate::graphql_vars! {@unexpected $($unexpected)+}; }; // Munch a token into the current key. (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { $crate::graphql_vars! { @object $object ($($key)* $tt) ($($rest)*) ($($rest)*) }; }; //////////// // Errors // //////////// (@unexpected) => {}; ////////////// // Defaults // ////////////// () => {{ $crate::Variables::<_>::new() }}; ( $($map:tt)+ ) => {{ let mut object = $crate::Variables::<_>::new(); $crate::graphql_vars! {@object object () ($($map)*) ($($map)*)}; object }}; } #[cfg(test)] mod tests { use indexmap::{indexmap, IndexMap}; type V = crate::Variables; type IV = crate::InputValue; #[test] fn empty() { assert_eq!(graphql_vars! {}, V::new()); } #[test] fn scalar() { let val = 42; assert_eq!( graphql_vars! {"key": 123}, vec![("key".into(), IV::scalar(123))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": "val"}, vec![("key".into(), IV::scalar("val"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": 1.23}, vec![("key".into(), IV::scalar(1.23))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": 1 + 2}, vec![("key".into(), IV::scalar(3))].into_iter().collect(), ); assert_eq!( graphql_vars! {"key": false}, vec![("key".into(), IV::scalar(false))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": (val)}, vec![("key".into(), IV::scalar(42))] .into_iter() .collect::(), ); } #[test] fn r#enum() { assert_eq!( graphql_vars! {"key": ENUM}, vec![("key".into(), IV::enum_value("ENUM"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": lowercase}, vec![("key".into(), IV::enum_value("lowercase"))] .into_iter() .collect::(), ); } #[test] fn variable() { assert_eq!( graphql_vars! {"key": @var}, vec![("key".into(), IV::variable("var"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": @array}, vec![("key".into(), IV::variable("array"))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": @object}, vec![("key".into(), IV::variable("object"))] .into_iter() .collect::(), ); } #[test] fn list() { let val = 42; assert_eq!( graphql_vars! {"key": []}, vec![("key".into(), IV::list(vec![]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [null]}, vec![("key".into(), IV::list(vec![IV::Null]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1]}, vec![("key".into(), IV::list(vec![IV::scalar(1)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1 + 2]}, vec![("key".into(), IV::list(vec![IV::scalar(3)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [(val)]}, vec![("key".into(), IV::list(vec![IV::scalar(42)]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [ENUM]}, vec![("key".into(), IV::list(vec![IV::enum_value("ENUM")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [lowercase]}, vec![("key".into(), IV::list(vec![IV::enum_value("lowercase")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [@var]}, vec![("key".into(), IV::list(vec![IV::variable("var")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [@array]}, vec![("key".into(), IV::list(vec![IV::variable("array")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [@object]}, vec![("key".into(), IV::list(vec![IV::variable("object")]))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1, [2], 3]}, vec![( "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::scalar(2)]), IV::scalar(3), ]), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1, [2 + 3], 3]}, vec![( "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::scalar(5)]), IV::scalar(3), ]), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1, [ENUM], (val)]}, vec![( "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::enum_value("ENUM")]), IV::scalar(42), ]), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1 + 2, [(val)], @val]}, vec![( "key".into(), IV::list(vec![ IV::scalar(3), IV::list(vec![IV::scalar(42)]), IV::variable("val"), ]), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": [1, [@val], ENUM]}, vec![( "key".into(), IV::list(vec![ IV::scalar(1), IV::list(vec![IV::variable("val")]), IV::enum_value("ENUM"), ]), )] .into_iter() .collect::(), ); } #[test] fn object() { let val = 42; assert_eq!( graphql_vars! {"key": {}}, vec![("key".into(), IV::object(IndexMap::::new()))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": null}}, vec![("key".into(), IV::object(indexmap! {"key" => IV::Null}))] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": 123}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::scalar(123)}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": 1 + 2}}, vec![("key".into(), IV::object(indexmap! {"key" => IV::scalar(3)}),)] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": (val)}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::scalar(42)}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": []}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![])}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": [null]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::Null])}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": [1]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(1)])}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": [1 + 2]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(3)])}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": [(val)]}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::list(vec![IV::scalar(42)])}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": ENUM}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::enum_value("ENUM")}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": lowercase}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::enum_value("lowercase")}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": @val}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::variable("val")}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! {"key": {"key": @array}}, vec![( "key".into(), IV::object(indexmap! {"key" => IV::variable("array")}), )] .into_iter() .collect::(), ); assert_eq!( graphql_vars! { "inner": { "key1": (val), "key2": "val", "key3": [{ "inner": 42, }, { "inner": ENUM, "even-more": { "var": @var, }, }], "key4": [1, ["val", 1 + 3], null, @array], }, "more": @var, }, vec![ ( "inner".into(), IV::object(indexmap! { "key1" => IV::scalar(42), "key2" => IV::scalar("val"), "key3" => IV::list(vec![ IV::object(indexmap! { "inner" => IV::scalar(42), }), IV::object(indexmap! { "inner" => IV::enum_value("ENUM"), "even-more" => IV::object(indexmap! { "var" => IV::variable("var"), }), }), ]), "key4" => IV::list(vec![ IV::scalar(1), IV::list(vec![ IV::scalar("val"), IV::scalar(4), ]), IV::Null, IV::variable("array"), ]), }), ), ("more".into(), IV::variable("var")), ] .into_iter() .collect::(), ); } } juniper-0.16.2/src/macros/helper/mod.rs000064400000000000000000000026731046102023000160550ustar 00000000000000//! Helper traits and definitions for macros. pub mod subscription; use std::fmt; use futures::future::{self, BoxFuture}; use crate::FieldError; /// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type /// from a [`Result`]. /// /// [`Error`]: Result::Error /// [`graphql_scalar!`]: macro@crate::graphql_scalar pub trait ExtractError { /// Extracted [`Error`] type of this [`Result`]. /// /// [`Error`]: Result::Error type Error; } impl ExtractError for Result { type Error = E; } /// Wraps `msg` with [`Display`] implementation into opaque [`Send`] [`Future`] /// which immediately resolves into [`FieldError`]. pub fn err_fut<'ok, D, Ok, S>(msg: D) -> BoxFuture<'ok, Result>> where D: fmt::Display, Ok: Send + 'ok, S: Send + 'static, { Box::pin(future::err(FieldError::from(msg))) } /// Generates a [`FieldError`] for the given Rust type expecting to have /// [`GraphQLType::name`]. /// /// [`GraphQLType::name`]: crate::GraphQLType::name pub fn err_unnamed_type(name: &str) -> FieldError { FieldError::from(format!( "Expected `{name}` type to implement `GraphQLType::name`", )) } /// Returns a [`future::err`] wrapping the [`err_unnamed_type`]. pub fn err_unnamed_type_fut<'ok, Ok, S>(name: &str) -> BoxFuture<'ok, Result>> where Ok: Send + 'ok, S: Send + 'static, { Box::pin(future::err(err_unnamed_type(name))) } juniper-0.16.2/src/macros/helper/subscription.rs000064400000000000000000000055031046102023000200150ustar 00000000000000//! Helper types for converting types to `Result>`. //! //! Used in `#[graphql_subscription]` macros to convert result type aliases on //! subscription handlers to a concrete return type. use futures::Stream; use crate::{FieldError, GraphQLValue, IntoFieldError, ScalarValue}; /// Trait for wrapping [`Stream`] into [`Ok`] if it's not [`Result`]. /// /// Used in subscription macros when user can provide type alias for [`Stream`] or /// `Result` and then a function on [`Stream`] should be called. pub trait IntoFieldResult { /// Type of items yielded by this [`Stream`]. type Item; /// Turns current [`Stream`] type into a generic [`Result`]. fn into_result(self) -> Result>; } impl IntoFieldResult for Result where T: IntoFieldResult, E: IntoFieldError, { type Item = T::Item; fn into_result(self) -> Result> { self.map_err(E::into_field_error) } } impl IntoFieldResult for T where T: Stream, { type Item = T::Item; fn into_result(self) -> Result> { Ok(self) } } /// This struct is used in `ExtractTypeFromStream` implementation for streams /// of values. pub struct StreamItem; /// This struct is used in `ExtractTypeFromStream` implementation for results /// with streams of values inside. pub struct StreamResult; /// This struct is used in `ExtractTypeFromStream` implementation for streams /// of results of values inside. pub struct ResultStreamItem; /// This struct is used in `ExtractTypeFromStream` implementation for results /// with streams of results of values inside. pub struct ResultStreamResult; /// This trait is used in `juniper::graphql_subscription` macro to get stream's /// item type that implements `GraphQLValue` from type alias provided /// by user. pub trait ExtractTypeFromStream where S: ScalarValue, { /// Stream's return Value that will be returned if /// no errors occured. Is used to determine field type in /// `#[juniper::graphql_subscription]` type Item: GraphQLValue; } impl ExtractTypeFromStream for T where T: futures::Stream, I: GraphQLValue, S: ScalarValue, { type Item = I; } impl ExtractTypeFromStream for Ty where Ty: futures::Stream>, T: GraphQLValue, S: ScalarValue, { type Item = T; } impl ExtractTypeFromStream for Result where T: futures::Stream, I: GraphQLValue, S: ScalarValue, { type Item = I; } impl ExtractTypeFromStream for Result where T: futures::Stream>, I: GraphQLValue, S: ScalarValue, { type Item = I; } juniper-0.16.2/src/macros/mod.rs000064400000000000000000000003701046102023000145660ustar 00000000000000//! Declarative macros and helper definitions for procedural macros. #[doc(hidden)] pub mod helper; #[doc(hidden)] #[macro_use] pub mod reflect; #[macro_use] mod graphql_input_value; #[macro_use] mod graphql_value; #[macro_use] mod graphql_vars; juniper-0.16.2/src/macros/reflect.rs000064400000000000000000001074461046102023000154470ustar 00000000000000//! Compile-time reflection of Rust types into GraphQL types. use std::{rc::Rc, sync::Arc}; use futures::future::BoxFuture; use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; /// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name /// in a GraphQL schema. /// /// See [`BaseType`] for more info. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub type Type = &'static str; /// Alias for a slice of [`Type`]s. /// /// See [`BaseSubTypes`] for more info. pub type Types = &'static [Type]; /// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// /// This trait is transparent to [`Option`], [`Vec`] and other containers, so to /// fully represent a [GraphQL object][1] we additionally use [`WrappedType`]. /// /// Different Rust types may have the same [`NAME`]. For example, [`String`] and /// `&`[`str`](prim@str) share `String!` GraphQL type. /// /// [`NAME`]: Self::NAME /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub trait BaseType { /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Interfaces const NAME: Type; } impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { const NAME: Type = T::NAME; } impl<'ctx, S, T> BaseType for (&'ctx T::Context, T) where S: ScalarValue, T: BaseType + GraphQLValue, { const NAME: Type = T::NAME; } impl> BaseType for Option { const NAME: Type = T::NAME; } impl> BaseType for Nullable { const NAME: Type = T::NAME; } impl, E> BaseType for Result { const NAME: Type = T::NAME; } impl> BaseType for Vec { const NAME: Type = T::NAME; } impl> BaseType for [T] { const NAME: Type = T::NAME; } impl, const N: usize> BaseType for [T; N] { const NAME: Type = T::NAME; } impl + ?Sized> BaseType for Box { const NAME: Type = T::NAME; } impl + ?Sized> BaseType for Arc { const NAME: Type = T::NAME; } impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } /// [Sub-types][2] of a [GraphQL object][1]. /// /// This trait is transparent to [`Option`], [`Vec`] and other containers. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC pub trait BaseSubTypes { /// Sub-[`Types`] of the [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects const NAMES: Types; } impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { const NAMES: Types = T::NAMES; } impl<'ctx, S, T> BaseSubTypes for (&'ctx T::Context, T) where S: ScalarValue, T: BaseSubTypes + GraphQLValue, { const NAMES: Types = T::NAMES; } impl> BaseSubTypes for Option { const NAMES: Types = T::NAMES; } impl> BaseSubTypes for Nullable { const NAMES: Types = T::NAMES; } impl, E> BaseSubTypes for Result { const NAMES: Types = T::NAMES; } impl> BaseSubTypes for Vec { const NAMES: Types = T::NAMES; } impl> BaseSubTypes for [T] { const NAMES: Types = T::NAMES; } impl, const N: usize> BaseSubTypes for [T; N] { const NAMES: Types = T::NAMES; } impl + ?Sized> BaseSubTypes for Box { const NAMES: Types = T::NAMES; } impl + ?Sized> BaseSubTypes for Arc { const NAMES: Types = T::NAMES; } impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } /// Alias for a value of a [`WrappedType`] (composed GraphQL type). pub type WrappedValue = u128; // TODO: Just use `&str`s once they're allowed in `const` generics. /// Encoding of a composed GraphQL type in numbers. /// /// To fully represent a [GraphQL object][1] it's not enough to use [`Type`], /// because of the [wrapping types][2]. To work around this we use a /// [`WrappedValue`] which is represented via [`u128`] number in the following /// encoding: /// - In base case of non-nullable [object][1] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so /// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// /// This approach allows us to uniquely represent any [GraphQL object][1] with a /// combination of [`Type`] and [`WrappedValue`] and even format it via /// [`format_type!`] macro in a `const` context. /// /// # Examples /// /// ```rust /// # use juniper::{ /// # format_type, /// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type}, /// # DefaultScalarValue, /// # }; /// # /// assert_eq!( as WrappedType>::VALUE, 12); /// assert_eq!( as WrappedType>::VALUE, 13); /// assert_eq!(> as WrappedType>::VALUE, 123); /// assert_eq!(> as WrappedType>::VALUE, 132); /// assert_eq!(>> as WrappedType>::VALUE, 1232); /// /// const TYPE_STRING: Type = >> as BaseType>::NAME; /// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; /// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); /// /// const TYPE_STR: Type = >> as BaseType>::NAME; /// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; /// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]"); /// ``` /// /// [`VALUE`]: Self::VALUE /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Wrapping-Types pub trait WrappedType { /// [`WrappedValue`] of this type. const VALUE: WrappedValue; } impl<'ctx, S, T: WrappedType> WrappedType for (&'ctx T::Context, T) where S: ScalarValue, T: GraphQLValue, { const VALUE: u128 = T::VALUE; } impl> WrappedType for Option { const VALUE: u128 = T::VALUE * 10 + 2; } impl> WrappedType for Nullable { const VALUE: u128 = T::VALUE * 10 + 2; } impl, E> WrappedType for Result { const VALUE: u128 = T::VALUE; } impl> WrappedType for Vec { const VALUE: u128 = T::VALUE * 10 + 3; } impl> WrappedType for [T] { const VALUE: u128 = T::VALUE * 10 + 3; } impl, const N: usize> WrappedType for [T; N] { const VALUE: u128 = T::VALUE * 10 + 3; } impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { const VALUE: u128 = T::VALUE; } impl + ?Sized> WrappedType for Box { const VALUE: u128 = T::VALUE; } impl + ?Sized> WrappedType for Arc { const VALUE: u128 = T::VALUE; } impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } /// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name. /// /// See [`Fields`] for more info. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces /// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments pub type Name = &'static str; /// Alias for a slice of [`Name`]s. /// /// See [`Fields`] for more info. pub type Names = &'static [Name]; /// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub type Argument = (Name, Type, WrappedValue); /// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and /// [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub type Arguments = &'static [(Name, Type, WrappedValue)]; /// Alias for a `const`-hashed [`Name`] used in a `const` context. pub type FieldName = u128; /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces /// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments pub trait Fields { /// [`Names`] of the [GraphQL object][1] or [interface][2] /// [field arguments][3]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces /// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments const NAMES: Names; } /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces pub trait Implements { /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces const NAMES: Types; } /// Stores meta information of a [GraphQL field][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. /// /// [`ARGUMENTS`]: Self::ARGUMENTS /// [`Context`]: Self::Context /// [`SUB_TYPES`]: Self::SUB_TYPES /// [`TYPE`]: Self::TYPE /// [`TypeInfo`]: Self::TypeInfo /// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub trait FieldMeta { /// [`GraphQLValue::Context`] of this [field][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields type Context; /// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields type TypeInfo; /// [`Types`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const TYPE: Type; /// [Sub-types][1] of [GraphQL field's][2] return type. /// /// [1]: BaseSubTypes /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields const SUB_TYPES: Types; /// [`WrappedValue`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; /// [GraphQL field's][1] [`Arguments`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const ARGUMENTS: Arguments; } /// Synchronous field of a [GraphQL object][1] or [interface][2]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub trait Field: FieldMeta { /// Resolves the [`Value`] of this synchronous [`Field`]. /// /// The `arguments` object contains all the specified arguments, with the /// default values being substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][1]. /// /// [`Value`]: crate::Value /// [1]: https://spec.graphql.org/October2021#sec-Objects fn call( &self, info: &Self::TypeInfo, args: &FieldArguments, executor: &Executor, ) -> ExecutionResult; } /// Asynchronous field of a GraphQL [object][1] or [interface][2]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub trait AsyncField: FieldMeta { /// Resolves the [`Value`] of this asynchronous [`AsyncField`]. /// /// The `arguments` object contains all the specified arguments, with the /// default values being substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects fn call<'b>( &'b self, info: &'b Self::TypeInfo, args: &'b FieldArguments, executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult>; } /// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in /// `const` generics. See [spec] for more info. /// /// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html #[must_use] pub const fn fnv1a128(str: Name) -> u128 { const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; let bytes = str.as_bytes(); let mut hash = FNV_OFFSET_BASIS; let mut i = 0; while i < bytes.len() { hash ^= bytes[i] as u128; hash = hash.wrapping_mul(FNV_PRIME); i += 1; } hash } /// Length __in bytes__ of the [`format_type!`] macro result. #[must_use] pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! let mut curr = val; while curr % 10 != 0 { match curr % 10 { 2 => len -= "!".as_bytes().len(), // remove ! 3 => len += "[]!".as_bytes().len(), // [Type]! _ => {} } curr /= 10; } len } /// Checks whether the given GraphQL [object][1] represents a `subtype` of the /// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding. /// /// To fully determine the sub-typing relation the [`Type`] should be one of the /// [`BaseSubTypes::NAMES`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { let ty_curr = ty % 10; let sub_curr = subtype % 10; if ty_curr == sub_curr { if ty_curr == 1 { true } else { can_be_subtype(ty / 10, subtype / 10) } } else if ty_curr == 2 { can_be_subtype(ty / 10, subtype) } else { false } } /// Checks whether the given `val` exists in the given `arr`. #[must_use] pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { let mut i = 0; while i < arr.len() { if str_eq(val, arr[i]) { return true; } i += 1; } false } /// Compares strings in a `const` context. /// /// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to /// write custom comparison function. /// /// [`Eq`]: std::cmp::Eq // TODO: Remove once `Eq` trait is allowed in `const` context. pub const fn str_eq(l: &str, r: &str) -> bool { let (l, r) = (l.as_bytes(), r.as_bytes()); if l.len() != r.len() { return false; } let mut i = 0; while i < l.len() { if l[i] != r[i] { return false; } i += 1; } true } /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. /// /// Symmetrical to [`assert_interfaces_impls!`]. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { const _: () = { $({ let is_present = $crate::macros::reflect::str_exists_in_arr( <$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, ); if !is_present { const MSG: &str = $crate::const_concat!( "Failed to implement interface `", <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`: missing implementer reference in interface's `for` attribute.", ); ::core::panic!("{}", MSG); } })* }; }; } /// Asserts that `impl = ...` attribute argument has all the types referencing /// this GraphQL type in `#[graphql_interface(for = ...)]`. /// /// Symmetrical to [`assert_implemented_for!`]. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { const _: () = { $({ let is_present = $crate::macros::reflect::str_exists_in_arr( <$interface as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES, ); if !is_present { const MSG: &str = $crate::const_concat!( "Failed to implement interface `", <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", <$implementers as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`: missing interface reference in implementer's `impl` attribute.", ); ::core::panic!("{}", MSG); } })* }; }; } /// 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, "`." ); ::core::panic!("{}", MSG); } })* }; }; } /// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. /// /// This assertion is a combination of [`assert_subtype`] and /// [`assert_field_args`]. /// /// See [spec][1] for more info. /// /// [1]: https://spec.graphql.org/October2021#IsValidImplementation() #[macro_export] macro_rules! assert_field { ( $base_ty: ty, $impl_ty: ty, $scalar: ty, $field_name: expr $(,)? ) => { $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); }; } /// Asserts validness of a [`Field`] return type. /// /// See [spec][1] for more info. /// /// [1]: https://spec.graphql.org/October2021#IsValidImplementationFieldType() #[macro_export] macro_rules! assert_subtype { ( $base_ty: ty, $impl_ty: ty, $scalar: ty, $field_name: expr $(,)? ) => { const _: () = { const BASE_TY: $crate::macros::reflect::Type = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const IMPL_TY: $crate::macros::reflect::Type = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const ERR_PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", BASE_TY, "` on `", IMPL_TY, "`: ", ); const FIELD_NAME: $crate::macros::reflect::Name = $field_name; const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue = <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue = <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const BASE_RETURN_TY: $crate::macros::reflect::Type = <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::reflect::Type = <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const BASE_RETURN_SUB_TYPES: $crate::macros::reflect::Types = <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( ERR_PREFIX, "Field `", FIELD_NAME, "`: implementor is expected to return a subtype of interface's return object: `", $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), "` is not a subtype of `", $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), "`.", ); ::core::panic!("{}", MSG); } }; }; } /// Asserts validness of the [`Field`]s arguments. See [spec][1] for more /// info. /// /// [1]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob #[macro_export] macro_rules! assert_field_args { ( $base_ty: ty, $impl_ty: ty, $scalar: ty, $field_name: expr $(,)? ) => { const _: () = { const BASE_NAME: &::core::primitive::str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const IMPL_NAME: &::core::primitive::str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const ERR_PREFIX: &::core::primitive::str = $crate::const_concat!( "Failed to implement interface `", BASE_NAME, "` on `", IMPL_NAME, "`: ", ); const FIELD_NAME: &::core::primitive::str = $field_name; const BASE_ARGS: ::juniper::macros::reflect::Arguments = <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; const IMPL_ARGS: ::juniper::macros::reflect::Arguments = <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { cause: Cause, base: ::juniper::macros::reflect::Argument, implementation: ::juniper::macros::reflect::Argument, } enum Cause { RequiredField, AdditionalNonNullableField, TypeMismatch, } const fn unwrap_error(v: ::core::result::Result<(), Error>) -> Error { match v { // Unfortunately we can't use `unreachable!()` here, as this // branch will be executed either way. ::core::result::Result::Ok(()) => Error { cause: Cause::RequiredField, base: ("unreachable", "unreachable", 1), implementation: ("unreachable", "unreachable", 1), }, ::core::result::Result::Err(err) => err, } } const fn check() -> ::core::result::Result<(), Error> { let mut base_i = 0; while base_i < BASE_ARGS.len() { let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; let mut impl_i = 0; let mut was_found = false; while impl_i < IMPL_ARGS.len() { let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; if $crate::macros::reflect::str_eq(base_name, impl_name) { if $crate::macros::reflect::str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { was_found = true; break; } else { return Err(Error { cause: Cause::TypeMismatch, base: (base_name, base_type, base_wrap_val), implementation: (impl_name, impl_type, impl_wrap_val), }); } } impl_i += 1; } if !was_found { return Err(Error { cause: Cause::RequiredField, base: (base_name, base_type, base_wrap_val), implementation: (base_name, base_type, base_wrap_val), }); } base_i += 1; } let mut impl_i = 0; while impl_i < IMPL_ARGS.len() { let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; impl_i += 1; if impl_wrapped_val % 10 == 2 { continue; } let mut base_i = 0; let mut was_found = false; while base_i < BASE_ARGS.len() { let (base_name, _, _) = BASE_ARGS[base_i]; if $crate::macros::reflect::str_eq(base_name, impl_name) { was_found = true; break; } base_i += 1; } if !was_found { return ::core::result::Result::Err(Error { cause: Cause::AdditionalNonNullableField, base: (impl_name, impl_type, impl_wrapped_val), implementation: (impl_name, impl_type, impl_wrapped_val), }); } } ::core::result::Result::Ok(()) } const RES: ::core::result::Result<(), Error> = check(); if RES.is_err() { const ERROR: Error = unwrap_error(RES); const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); const MSG: &::core::primitive::str = match ERROR.cause { Cause::TypeMismatch => { $crate::const_concat!( "Argument `", BASE_ARG_NAME, "`: expected type `", BASE_TYPE_FORMATTED, "`, found: `", IMPL_TYPE_FORMATTED, "`.", ) } Cause::RequiredField => { $crate::const_concat!( "Argument `", BASE_ARG_NAME, "` of type `", BASE_TYPE_FORMATTED, "` was expected, but not found." ) } Cause::AdditionalNonNullableField => { $crate::const_concat!( "Argument `", IMPL_ARG_NAME, "` of type `", IMPL_TYPE_FORMATTED, "` isn't present on the interface and so has to be nullable." ) } }; const ERROR_MSG: &str = $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); ::core::panic!("{}", ERROR_MSG); } }; }; } /// Concatenates `const` [`str`](prim@str)s in a `const` context. #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ const LEN: ::core::primitive::usize = 0 $(+ $s.as_bytes().len())*; const CNT: ::core::primitive::usize = [$($s),*].len(); const fn concat(input: [&::core::primitive::str; CNT]) -> [::core::primitive::u8; LEN] { let mut bytes = [0; LEN]; let (mut i, mut byte) = (0, 0); while i < CNT { let mut b = 0; while b < input[i].len() { bytes[byte] = input[i].as_bytes()[b]; byte += 1; b += 1; } i += 1; } bytes } const CON: [::core::primitive::u8; LEN] = concat([$($s),*]); // TODO: Use `.unwrap()` once it becomes `const`. match ::core::str::from_utf8(&CON) { ::core::result::Result::Ok(s) => s, _ => ::core::unreachable!(), } }}; } /// Ensures that the given `$impl_ty` implements [`Field`] and returns a /// [`fnv1a128`] hash for it, otherwise panics with understandable message. #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ let exists = $crate::macros::reflect::str_exists_in_arr( $field_name, <$impl_ty as $crate::macros::reflect::Fields<$scalar>>::NAMES, ); if exists { $crate::macros::reflect::fnv1a128(FIELD_NAME) } else { const MSG: &str = $crate::const_concat!( $($prefix,)? "Field `", $field_name, "` isn't implemented on `", <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`." ); ::core::panic!("{}", MSG) } }}; } /// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type /// name. /// /// # Examples /// /// ```rust /// # use juniper::format_type; /// # /// assert_eq!(format_type!("String", 123), "[String]!"); /// assert_eq!(format_type!("🦀", 123), "[🦀]!"); /// ``` #[macro_export] macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ const TYPE: ( $crate::macros::reflect::Type, $crate::macros::reflect::WrappedValue, ) = ($ty, $wrapped_value); const RES_LEN: usize = $crate::macros::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &::core::primitive::str = "["; const CLOSING_BRACKET: &::core::primitive::str = "]"; const BANG: &::core::primitive::str = "!"; const fn format_type_arr() -> [::core::primitive::u8; RES_LEN] { let (ty, wrap_val) = TYPE; let mut type_arr: [::core::primitive::u8; RES_LEN] = [0; RES_LEN]; let mut current_start = 0; let mut current_end = RES_LEN - 1; let mut current_wrap_val = wrap_val; let mut is_null = false; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { 2 => is_null = true, // Skips writing `BANG` later. 3 => { // Write `OPENING_BRACKET` at `current_start`. let mut i = 0; while i < OPENING_BRACKET.as_bytes().len() { type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; i += 1; } current_start += i; if !is_null { // Write `BANG` at `current_end`. i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; } current_end -= i; } // Write `CLOSING_BRACKET` at `current_end`. i = 0; while i < CLOSING_BRACKET.as_bytes().len() { type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = CLOSING_BRACKET.as_bytes()[i]; i += 1; } current_end -= i; is_null = false; } _ => {} } current_wrap_val /= 10; } // Writes `Type` at `current_start`. let mut i = 0; while i < ty.as_bytes().len() { type_arr[current_start + i] = ty.as_bytes()[i]; i += 1; } i = 0; if !is_null { // Writes `BANG` at `current_end`. while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; } } type_arr } const TYPE_ARR: [::core::primitive::u8; RES_LEN] = format_type_arr(); // TODO: Use `.unwrap()` once it becomes `const`. const TYPE_FORMATTED: &::core::primitive::str = match ::core::str::from_utf8(TYPE_ARR.as_slice()) { ::core::result::Result::Ok(s) => s, _ => unreachable!(), }; TYPE_FORMATTED }}; } juniper-0.16.2/src/parser/document.rs000064400000000000000000000377131046102023000156500ustar 00000000000000use std::borrow::Cow; use crate::ast::{ Arguments, Definition, Directive, Field, Fragment, FragmentSpread, InlineFragment, InputValue, Operation, OperationType, OwnedDocument, Selection, Type, VariableDefinition, VariableDefinitions, }; use crate::{ parser::{ value::parse_value_literal, Lexer, OptionParseResult, ParseError, ParseResult, Parser, Spanning, Token, UnlocatedParseResult, }, schema::{ meta::{Argument, Field as MetaField}, model::SchemaType, }, value::ScalarValue, }; #[doc(hidden)] pub fn parse_document_source<'a, 'b, S>( s: &'a str, schema: &'b SchemaType<'b, S>, ) -> UnlocatedParseResult> where S: ScalarValue, { let mut lexer = Lexer::new(s); let mut parser = Parser::new(&mut lexer).map_err(|s| s.map(ParseError::LexerError))?; parse_document(&mut parser, schema) } fn parse_document<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> UnlocatedParseResult> where S: ScalarValue, { let mut defs = Vec::new(); loop { defs.push(parse_definition(parser, schema)?); if parser.peek().item == Token::EndOfFile { return Ok(defs); } } } fn parse_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> UnlocatedParseResult> where S: ScalarValue, { match parser.peek().item { Token::CurlyOpen | Token::Name("query") | Token::Name("mutation") | Token::Name("subscription") => Ok(Definition::Operation(parse_operation_definition( parser, schema, )?)), Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition( parser, schema, )?)), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_operation_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> ParseResult> where S: ScalarValue, { if parser.peek().item == Token::CurlyOpen { let fields = schema.concrete_query_type().fields(schema); let fields = fields.as_ref().map(|c| c as &[_]); let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Spanning::new( selection_set.span, Operation { operation_type: OperationType::Query, name: None, variable_definitions: None, directives: None, selection_set: selection_set.item, }, )) } else { let start_pos = parser.peek().span.start; let operation_type = parse_operation_type(parser)?; let op = match operation_type.item { OperationType::Query => Some(schema.concrete_query_type()), OperationType::Mutation => schema.concrete_mutation_type(), OperationType::Subscription => schema.concrete_subscription_type(), }; let fields = op.and_then(|m| m.fields(schema)); let fields = fields.as_ref().map(|c| c as &[_]); let name = match parser.peek().item { Token::Name(_) => Some(parser.expect_name()?), _ => None, }; let variable_definitions = parse_variable_definitions(parser, schema)?; let directives = parse_directives(parser, schema)?; let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Spanning::start_end( &start_pos, &selection_set.span.end, Operation { operation_type: operation_type.item, name, variable_definitions, directives: directives.map(|s| s.item), selection_set: selection_set.item, }, )) } } fn parse_fragment_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> ParseResult> where S: ScalarValue, { let start_pos = parser.expect(&Token::Name("fragment"))?.span.start; let name = match parser.expect_name() { Ok(n) => { if n.item == "on" { return Err(n.map(|_| ParseError::UnexpectedToken("on".into()))); } else { n } } Err(e) => return Err(e), }; parser.expect(&Token::Name("on"))?; let type_cond = parser.expect_name()?; let fields = schema .concrete_type_by_name(type_cond.item) .and_then(|m| m.fields(schema)); let fields = fields.as_ref().map(|c| c as &[_]); let directives = parse_directives(parser, schema)?; let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Spanning::start_end( &start_pos, &selection_set.span.end, Fragment { name, type_condition: type_cond, directives: directives.map(|s| s.item), selection_set: selection_set.item, }, )) } fn parse_optional_selection_set<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, ) -> OptionParseResult>> where S: ScalarValue, { if parser.peek().item == Token::CurlyOpen { Ok(Some(parse_selection_set(parser, schema, fields)?)) } else { Ok(None) } } fn parse_selection_set<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, ) -> ParseResult>> where S: ScalarValue, { parser.unlocated_delimited_nonempty_list( &Token::CurlyOpen, |p| parse_selection(p, schema, fields), &Token::CurlyClose, ) } fn parse_selection<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, ) -> UnlocatedParseResult> where S: ScalarValue, { match parser.peek().item { Token::Ellipsis => parse_fragment(parser, schema, fields), _ => parse_field(parser, schema, fields).map(Selection::Field), } } fn parse_fragment<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, ) -> UnlocatedParseResult> where S: ScalarValue, { let start_pos = parser.expect(&Token::Ellipsis)?.span.start; match parser.peek().item { Token::Name("on") => { parser.next_token()?; let name = parser.expect_name()?; let fields = schema .concrete_type_by_name(name.item) .and_then(|m| m.fields(schema)); let fields = fields.as_ref().map(|c| c as &[_]); let directives = parse_directives(parser, schema)?; let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Selection::InlineFragment(Spanning::start_end( &start_pos, &selection_set.span.end, InlineFragment { type_condition: Some(name), directives: directives.map(|s| s.item), selection_set: selection_set.item, }, ))) } Token::CurlyOpen => { let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Selection::InlineFragment(Spanning::start_end( &start_pos, &selection_set.span.end, InlineFragment { type_condition: None, directives: None, selection_set: selection_set.item, }, ))) } Token::Name(_) => { let frag_name = parser.expect_name()?; let directives = parse_directives(parser, schema)?; Ok(Selection::FragmentSpread(Spanning::start_end( &start_pos.clone(), &directives .as_ref() .map_or(&frag_name.span.end, |s| &s.span.end) .clone(), FragmentSpread { name: frag_name, directives: directives.map(|s| s.item), }, ))) } Token::At => { let directives = parse_directives(parser, schema)?; let selection_set = parse_selection_set(parser, schema, fields)?; Ok(Selection::InlineFragment(Spanning::start_end( &start_pos, &selection_set.span.end, InlineFragment { type_condition: None, directives: directives.map(|s| s.item), selection_set: selection_set.item, }, ))) } _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_field<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, fields: Option<&[&MetaField<'b, S>]>, ) -> ParseResult> where S: ScalarValue, { let mut alias = Some(parser.expect_name()?); let name = if parser.skip(&Token::Colon)?.is_some() { parser.expect_name()? } else { alias.take().unwrap() }; let field = fields.and_then(|f| f.iter().find(|f| f.name == name.item)); let args = field .as_ref() .and_then(|f| f.arguments.as_ref().map(|a| a as &[_])); let fields = field .as_ref() .and_then(|f| schema.lookup_type(&f.field_type)) .and_then(|m| m.fields(schema)); let fields = fields.as_ref().map(|c| c as &[_]); let arguments = parse_arguments(parser, schema, args)?; let directives = parse_directives(parser, schema)?; let selection_set = parse_optional_selection_set(parser, schema, fields)?; Ok(Spanning::start_end( &alias.as_ref().unwrap_or(&name).span.start, &selection_set .as_ref() .map(|s| &s.span.end) .or_else(|| directives.as_ref().map(|s| &s.span.end)) .or_else(|| arguments.as_ref().map(|s| &s.span.end)) .unwrap_or(&name.span.end) .clone(), Field { alias, name, arguments, directives: directives.map(|s| s.item), selection_set: selection_set.map(|s| s.item), }, )) } fn parse_arguments<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, arguments: Option<&[Argument<'b, S>]>, ) -> OptionParseResult> where S: ScalarValue, { if parser.peek().item != Token::ParenOpen { Ok(None) } else { Ok(Some( parser .delimited_nonempty_list( &Token::ParenOpen, |p| parse_argument(p, schema, arguments), &Token::ParenClose, )? .map(|args| Arguments { items: args.into_iter().map(|s| s.item).collect(), }), )) } } fn parse_argument<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, arguments: Option<&[Argument<'b, S>]>, ) -> ParseResult<(Spanning<&'a str>, Spanning>)> where S: ScalarValue, { let name = parser.expect_name()?; let tpe = arguments .and_then(|args| args.iter().find(|a| a.name == name.item)) .and_then(|arg| schema.lookup_type(&arg.arg_type)); parser.expect(&Token::Colon)?; let value = parse_value_literal(parser, false, schema, tpe)?; Ok(Spanning::start_end( &name.span.start, &value.span.end.clone(), (name, value), )) } fn parse_operation_type(parser: &mut Parser<'_>) -> ParseResult { match parser.peek().item { Token::Name("query") => Ok(parser.next_token()?.map(|_| OperationType::Query)), Token::Name("mutation") => Ok(parser.next_token()?.map(|_| OperationType::Mutation)), Token::Name("subscription") => { Ok(parser.next_token()?.map(|_| OperationType::Subscription)) } _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_variable_definitions<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> OptionParseResult> where S: ScalarValue, { if parser.peek().item != Token::ParenOpen { Ok(None) } else { Ok(Some( parser .delimited_nonempty_list( &Token::ParenOpen, |p| parse_variable_definition(p, schema), &Token::ParenClose, )? .map(|defs| VariableDefinitions { items: defs.into_iter().map(|s| s.item).collect(), }), )) } } fn parse_variable_definition<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> ParseResult<(Spanning<&'a str>, VariableDefinition<'a, S>)> where S: ScalarValue, { let start_pos = parser.expect(&Token::Dollar)?.span.start; let var_name = parser.expect_name()?; parser.expect(&Token::Colon)?; let var_type = parse_type(parser)?; let tpe = schema.lookup_type(&var_type.item); let default_value = if parser.skip(&Token::Equals)?.is_some() { Some(parse_value_literal(parser, true, schema, tpe)?) } else { None }; let directives = parse_directives(parser, schema)?; Ok(Spanning::start_end( &start_pos, &default_value .as_ref() .map_or(&var_type.span.end, |s| &s.span.end) .clone(), ( Spanning::start_end(&start_pos, &var_name.span.end, var_name.item), VariableDefinition { var_type, default_value, directives: directives.map(|s| s.item), }, ), )) } fn parse_directives<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> OptionParseResult>>> where S: ScalarValue, { if parser.peek().item != Token::At { Ok(None) } else { let mut items = Vec::new(); while parser.peek().item == Token::At { items.push(parse_directive(parser, schema)?); } Ok(Spanning::spanning(items)) } } fn parse_directive<'a, 'b, S>( parser: &mut Parser<'a>, schema: &'b SchemaType<'b, S>, ) -> ParseResult> where S: ScalarValue, { let start_pos = parser.expect(&Token::At)?.span.start; let name = parser.expect_name()?; let directive = schema.directive_by_name(name.item); let arguments = parse_arguments( parser, schema, directive.as_ref().map(|d| &d.arguments as &[_]), )?; Ok(Spanning::start_end( &start_pos, &arguments .as_ref() .map_or(&name.span.end, |s| &s.span.end) .clone(), Directive { name, arguments }, )) } pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult> { let parsed_type = if let Some(Spanning { span: ref start_span, .. }) = parser.skip(&Token::BracketOpen)? { let inner_type = parse_type(parser)?; let end_pos = parser.expect(&Token::BracketClose)?.span.end; Spanning::start_end( &start_span.start, &end_pos, Type::List(Box::new(inner_type.item), None), ) } else { parser.expect_name()?.map(|s| Type::Named(Cow::Borrowed(s))) }; Ok(match *parser.peek() { Spanning { item: Token::ExclamationMark, .. } => wrap_non_null(parser, parsed_type)?, _ => parsed_type, }) } fn wrap_non_null<'a>(parser: &mut Parser<'a>, inner: Spanning>) -> ParseResult> { let end_pos = &parser.expect(&Token::ExclamationMark)?.span.end; let wrapped = match inner.item { Type::Named(name) => Type::NonNullNamed(name), Type::List(l, expected_size) => Type::NonNullList(l, expected_size), t => t, }; Ok(Spanning::start_end(&inner.span.start, end_pos, wrapped)) } juniper-0.16.2/src/parser/lexer.rs000064400000000000000000000431431046102023000151430ustar 00000000000000use std::{char, fmt, iter::Peekable, str::CharIndices}; use crate::parser::{SourcePosition, Spanning}; #[doc(hidden)] #[derive(Debug)] pub struct Lexer<'a> { iterator: Peekable>, source: &'a str, length: usize, position: SourcePosition, has_reached_eof: bool, } /// A single scalar value literal /// /// This is only used for tagging how the lexer has interpreted a value literal #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ScalarToken<'a> { String(&'a str), Float(&'a str), Int(&'a str), } /// A single token in the input source #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Token<'a> { Name(&'a str), Scalar(ScalarToken<'a>), ExclamationMark, Dollar, ParenOpen, ParenClose, BracketOpen, BracketClose, CurlyOpen, CurlyClose, Ellipsis, Colon, Equals, At, Pipe, EndOfFile, } /// Error when tokenizing the input source #[derive(Clone, Debug, PartialEq, Eq)] pub enum LexerError { /// An unknown character was found /// /// Unknown characters are characters that do not occur anywhere in the /// GraphQL language, such as `?` or `%`. UnknownCharacter(char), /// An unexpected character was found /// /// Unexpected characters are characters that _do_ exist in the GraphQL /// language, but is not expected at the current position in the document. UnexpectedCharacter(char), /// An unterminated string literal was found /// /// Apart from forgetting the ending `"`, terminating a string within a /// Unicode escape sequence or having a line break in the string also /// causes this error. UnterminatedString, /// An unknown character in a string literal was found /// /// This occurs when an invalid source character is found in a string /// literal, such as ASCII control characters. UnknownCharacterInString(char), /// An unknown escape sequence in a string literal was found /// /// Only a limited set of escape sequences are supported, this is emitted /// when e.g. `"\l"` is parsed. UnknownEscapeSequence(String), /// The input source was unexpectedly terminated /// /// Emitted when the current token requires a succeeding character, but /// the source has reached EOF. Emitted when scanning e.g. `"1."`. UnexpectedEndOfFile, /// An invalid number literal was found InvalidNumber, } pub type LexerResult<'a> = Result>, Spanning>; impl<'a> Lexer<'a> { #[doc(hidden)] pub fn new(source: &'a str) -> Lexer<'a> { Lexer { iterator: source.char_indices().peekable(), source, length: source.len(), position: SourcePosition::new_origin(), has_reached_eof: false, } } fn peek_char(&mut self) -> Option<(usize, char)> { assert!(self.position.index() <= self.length); assert!(!self.has_reached_eof); self.iterator.peek().map(|&(idx, ch)| (idx, ch)) } fn next_char(&mut self) -> Option<(usize, char)> { assert!(self.position.index() <= self.length); assert!(!self.has_reached_eof); let next = self.iterator.next(); if let Some((_, ch)) = next { if ch == '\n' { self.position.advance_line(); } else { self.position.advance_col(); } } next } fn emit_single_char(&mut self, t: Token<'a>) -> Spanning> { assert!(self.position.index() <= self.length); let start_pos = self.position; self.next_char() .expect("Internal error in GraphQL lexer: emit_single_char reached EOF"); Spanning::single_width(&start_pos, t) } fn scan_over_whitespace(&mut self) { while let Some((_, ch)) = self.peek_char() { if ch == '\t' || ch == ' ' || ch == '\n' || ch == '\r' || ch == ',' { self.next_char(); } else if ch == '#' { self.next_char(); while let Some((_, ch)) = self.peek_char() { if is_source_char(ch) && (ch == '\n' || ch == '\r') { self.next_char(); break; } else if is_source_char(ch) { self.next_char(); } else { break; } } } else { break; } } } fn scan_ellipsis(&mut self) -> LexerResult<'a> { let start_pos = self.position; for _ in 0..3 { let (_, ch) = self.next_char().ok_or_else(|| { Spanning::zero_width(&self.position, LexerError::UnexpectedEndOfFile) })?; if ch != '.' { return Err(Spanning::zero_width( &start_pos, LexerError::UnexpectedCharacter('.'), )); } } Ok(Spanning::start_end( &start_pos, &self.position, Token::Ellipsis, )) } fn scan_name(&mut self) -> LexerResult<'a> { let start_pos = self.position; let (start_idx, start_ch) = self .next_char() .ok_or_else(|| Spanning::zero_width(&self.position, LexerError::UnexpectedEndOfFile))?; assert!(is_name_start(start_ch)); let mut end_idx = start_idx; while let Some((idx, ch)) = self.peek_char() { if is_name_cont(ch) { self.next_char(); end_idx = idx; } else { break; } } Ok(Spanning::start_end( &start_pos, &self.position, Token::Name(&self.source[start_idx..=end_idx]), )) } fn scan_string(&mut self) -> LexerResult<'a> { let start_pos = self.position; let (start_idx, start_ch) = self .next_char() .ok_or_else(|| Spanning::zero_width(&self.position, LexerError::UnexpectedEndOfFile))?; if start_ch != '"' { return Err(Spanning::zero_width( &self.position, LexerError::UnterminatedString, )); } let mut escaped = false; let mut old_pos = self.position; while let Some((idx, ch)) = self.next_char() { match ch { 'b' | 'f' | 'n' | 'r' | 't' | '\\' | '/' | '"' if escaped => { escaped = false; } 'u' if escaped => { self.scan_escaped_unicode(&old_pos)?; escaped = false; } c if escaped => { return Err(Spanning::zero_width( &old_pos, LexerError::UnknownEscapeSequence(format!("\\{c}")), )); } '\\' => escaped = true, '"' if !escaped => { return Ok(Spanning::start_end( &start_pos, &self.position, Token::Scalar(ScalarToken::String(&self.source[start_idx + 1..idx])), )); } '\n' | '\r' => { return Err(Spanning::zero_width( &old_pos, LexerError::UnterminatedString, )); } c if !is_source_char(c) => { return Err(Spanning::zero_width( &old_pos, LexerError::UnknownCharacterInString(ch), )); } _ => {} } old_pos = self.position; } Err(Spanning::zero_width( &self.position, LexerError::UnterminatedString, )) } fn scan_escaped_unicode( &mut self, start_pos: &SourcePosition, ) -> Result<(), Spanning> { let (start_idx, _) = self .peek_char() .ok_or_else(|| Spanning::zero_width(&self.position, LexerError::UnterminatedString))?; let mut end_idx = start_idx; let mut len = 0; for _ in 0..4 { let (idx, ch) = self.next_char().ok_or_else(|| { Spanning::zero_width(&self.position, LexerError::UnterminatedString) })?; if !ch.is_alphanumeric() { break; } end_idx = idx; len += 1; } // Make sure we are on a valid char boundary. let escape = self .source .get(start_idx..=end_idx) .ok_or_else(|| Spanning::zero_width(&self.position, LexerError::UnterminatedString))?; if len != 4 { return Err(Spanning::zero_width( start_pos, LexerError::UnknownEscapeSequence(format!("\\u{escape}")), )); } let code_point = u32::from_str_radix(escape, 16).map_err(|_| { Spanning::zero_width( start_pos, LexerError::UnknownEscapeSequence(format!("\\u{escape}")), ) })?; char::from_u32(code_point) .ok_or_else(|| { Spanning::zero_width( start_pos, LexerError::UnknownEscapeSequence("\\u".to_owned() + escape), ) }) .map(|_| ()) } fn scan_number(&mut self) -> LexerResult<'a> { let start_pos = self.position; let (start_idx, _) = self .peek_char() .ok_or_else(|| Spanning::zero_width(&self.position, LexerError::UnexpectedEndOfFile))?; let mut last_idx = start_idx; let mut last_char = '1'; let mut is_float = false; let mut end_idx = loop { if let Some((idx, ch)) = self.peek_char() { if ch.is_ascii_digit() || (ch == '-' && last_idx == start_idx) { if ch == '0' && last_char == '0' && last_idx == start_idx { return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedCharacter('0'), )); } self.next_char(); last_char = ch; } else if last_char == '-' { return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedCharacter(ch), )); } else { break idx; } last_idx = idx; } else { break last_idx + 1; } }; if let Some((start_idx, '.')) = self.peek_char() { is_float = true; let mut last_idx = start_idx; self.next_char(); end_idx = loop { if let Some((idx, ch)) = self.peek_char() { if ch.is_ascii_digit() { self.next_char(); } else if last_idx == start_idx { return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedCharacter(ch), )); } else { break idx; } last_idx = idx; } else if last_idx == start_idx { return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedEndOfFile, )); } else { break last_idx + 1; } }; } if let Some((start_idx, ch)) = self.peek_char() { if ch == 'e' || ch == 'E' { is_float = true; self.next_char(); let mut last_idx = start_idx; end_idx = loop { if let Some((idx, ch)) = self.peek_char() { if ch.is_ascii_digit() || (last_idx == start_idx && (ch == '-' || ch == '+')) { self.next_char(); } else if last_idx == start_idx { // 1e is not a valid floating point number return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedCharacter(ch), )); } else { break idx; } last_idx = idx; } else if last_idx == start_idx { // 1e is not a valid floting point number return Err(Spanning::zero_width( &self.position, LexerError::UnexpectedEndOfFile, )); } else { break last_idx + 1; } }; } } let number = &self.source[start_idx..end_idx]; let end_pos = &self.position; let token = if is_float { Token::Scalar(ScalarToken::Float(number)) } else { Token::Scalar(ScalarToken::Int(number)) }; Ok(Spanning::start_end(&start_pos, end_pos, token)) } } impl<'a> Iterator for Lexer<'a> { type Item = LexerResult<'a>; fn next(&mut self) -> Option { if self.has_reached_eof { return None; } self.scan_over_whitespace(); let ch = self.iterator.peek().map(|&(_, ch)| ch); Some(match ch { Some('!') => Ok(self.emit_single_char(Token::ExclamationMark)), Some('$') => Ok(self.emit_single_char(Token::Dollar)), Some('(') => Ok(self.emit_single_char(Token::ParenOpen)), Some(')') => Ok(self.emit_single_char(Token::ParenClose)), Some('[') => Ok(self.emit_single_char(Token::BracketOpen)), Some(']') => Ok(self.emit_single_char(Token::BracketClose)), Some('{') => Ok(self.emit_single_char(Token::CurlyOpen)), Some('}') => Ok(self.emit_single_char(Token::CurlyClose)), Some(':') => Ok(self.emit_single_char(Token::Colon)), Some('=') => Ok(self.emit_single_char(Token::Equals)), Some('@') => Ok(self.emit_single_char(Token::At)), Some('|') => Ok(self.emit_single_char(Token::Pipe)), Some('.') => self.scan_ellipsis(), Some('"') => self.scan_string(), Some(ch) => { if is_number_start(ch) { self.scan_number() } else if is_name_start(ch) { self.scan_name() } else { Err(Spanning::zero_width( &self.position, LexerError::UnknownCharacter(ch), )) } } None => { self.has_reached_eof = true; Ok(Spanning::zero_width(&self.position, Token::EndOfFile)) } }) } } impl<'a> fmt::Display for Token<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Token::Name(name) => write!(f, "{name}"), Token::Scalar(ScalarToken::Int(s)) | Token::Scalar(ScalarToken::Float(s)) => { write!(f, "{s}") } Token::Scalar(ScalarToken::String(s)) => { write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) } Token::ExclamationMark => write!(f, "!"), Token::Dollar => write!(f, "$"), Token::ParenOpen => write!(f, "("), Token::ParenClose => write!(f, ")"), Token::BracketOpen => write!(f, "["), Token::BracketClose => write!(f, "]"), Token::CurlyOpen => write!(f, "{{"), Token::CurlyClose => write!(f, "}}"), Token::Ellipsis => write!(f, "..."), Token::Colon => write!(f, ":"), Token::Equals => write!(f, "="), Token::At => write!(f, "@"), Token::Pipe => write!(f, "|"), Token::EndOfFile => write!(f, "End of file"), } } } fn is_source_char(c: char) -> bool { c == '\t' || c == '\n' || c == '\r' || c >= ' ' } fn is_name_start(c: char) -> bool { c == '_' || c.is_ascii_alphabetic() } fn is_name_cont(c: char) -> bool { is_name_start(c) || c.is_ascii_digit() } fn is_number_start(c: char) -> bool { c == '-' || c.is_ascii_digit() } impl fmt::Display for LexerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LexerError::UnknownCharacter(c) => write!(f, "Unknown character \"{c}\""), LexerError::UnterminatedString => write!(f, "Unterminated string literal"), LexerError::UnknownCharacterInString(c) => { write!(f, "Unknown character \"{c}\" in string literal") } LexerError::UnknownEscapeSequence(ref s) => { write!(f, "Unknown escape sequence \"{s}\" in string") } LexerError::UnexpectedCharacter(c) => write!(f, "Unexpected character \"{c}\""), LexerError::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), LexerError::InvalidNumber => write!(f, "Invalid number literal"), } } } impl std::error::Error for LexerError {} juniper-0.16.2/src/parser/mod.rs000064400000000000000000000006361046102023000146030ustar 00000000000000//! Query parser and language utilities #![allow(clippy::module_inception)] mod document; mod lexer; mod parser; mod utils; mod value; #[cfg(test)] mod tests; pub use self::document::parse_document_source; pub use self::{ lexer::{Lexer, LexerError, ScalarToken, Token}, parser::{OptionParseResult, ParseError, ParseResult, Parser, UnlocatedParseResult}, utils::{SourcePosition, Span, Spanning}, }; juniper-0.16.2/src/parser/parser.rs000064400000000000000000000141461046102023000153210ustar 00000000000000use std::{error::Error, fmt}; use smartstring::alias::String; use crate::parser::{Lexer, LexerError, Spanning, Token}; /// Error while parsing a GraphQL query #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParseError { /// An unexpected token occurred in the source // TODO: Previously was `Token<'a>`. // Revisit on `graphql-parser` integration. UnexpectedToken(String), /// The input source abruptly ended UnexpectedEndOfFile, /// An error during tokenization occurred LexerError(LexerError), /// A scalar of unexpected type occurred in the source ExpectedScalarError(&'static str), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::UnexpectedToken(token) => write!(f, "Unexpected \"{token}\""), Self::UnexpectedEndOfFile => write!(f, "Unexpected end of input"), Self::LexerError(e) => e.fmt(f), Self::ExpectedScalarError(e) => e.fmt(f), } } } impl Error for ParseError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::LexerError(e) => Some(e), Self::ExpectedScalarError(_) | Self::UnexpectedToken(_) | Self::UnexpectedEndOfFile => { None } } } } impl ParseError { /// Creates a [`ParseError::UnexpectedToken`] out of the provided [`Token`]. #[must_use] pub fn unexpected_token(token: Token<'_>) -> Self { use std::fmt::Write as _; let mut s = String::new(); // PANIC: Unwrapping is OK here, as it may panic only on allocation // error. write!(s, "{token}").unwrap(); Self::UnexpectedToken(s) } } #[doc(hidden)] pub type ParseResult = Result, Spanning>; #[doc(hidden)] pub type UnlocatedParseResult = Result>; #[doc(hidden)] pub type OptionParseResult = Result>, Spanning>; #[doc(hidden)] #[derive(Debug)] pub struct Parser<'a> { tokens: Vec>>, } impl<'a> Parser<'a> { #[doc(hidden)] pub fn new(lexer: &mut Lexer<'a>) -> Result, Spanning> { let mut tokens = Vec::new(); for res in lexer { match res { Ok(s) => tokens.push(s), Err(e) => return Err(e), } } Ok(Parser { tokens }) } #[doc(hidden)] pub fn peek(&self) -> &Spanning> { &self.tokens[0] } #[doc(hidden)] pub fn next_token(&mut self) -> ParseResult> { if self.tokens.len() == 1 { Err(Spanning::new( self.peek().span, ParseError::UnexpectedEndOfFile, )) } else { Ok(self.tokens.remove(0)) } } #[doc(hidden)] pub fn expect(&mut self, expected: &Token) -> ParseResult> { if &self.peek().item != expected { Err(self.next_token()?.map(ParseError::unexpected_token)) } else { self.next_token() } } #[doc(hidden)] pub fn skip( &mut self, expected: &Token, ) -> Result>>, Spanning> { if &self.peek().item == expected { Ok(Some(self.next_token()?)) } else if self.peek().item == Token::EndOfFile { Err(Spanning::zero_width( &self.peek().span.start, ParseError::UnexpectedEndOfFile, )) } else { Ok(None) } } #[doc(hidden)] pub fn delimited_list( &mut self, opening: &Token, parser: F, closing: &Token, ) -> ParseResult>> where T: fmt::Debug, F: Fn(&mut Parser<'a>) -> ParseResult, { let start_pos = &self.expect(opening)?.span.start; let mut items = Vec::new(); loop { if let Some(Spanning { span, .. }) = self.skip(closing)? { return Ok(Spanning::start_end(start_pos, &span.end, items)); } items.push(parser(self)?); } } #[doc(hidden)] pub fn delimited_nonempty_list( &mut self, opening: &Token, parser: F, closing: &Token, ) -> ParseResult>> where T: fmt::Debug, F: Fn(&mut Parser<'a>) -> ParseResult, { let start_pos = &self.expect(opening)?.span.start; let mut items = Vec::new(); loop { items.push(parser(self)?); if let Some(end_spanning) = self.skip(closing)? { return Ok(Spanning::start_end(start_pos, &end_spanning.end(), items)); } } } #[doc(hidden)] pub fn unlocated_delimited_nonempty_list( &mut self, opening: &Token, parser: F, closing: &Token, ) -> ParseResult> where T: fmt::Debug, F: Fn(&mut Parser<'a>) -> UnlocatedParseResult, { let start_pos = &self.expect(opening)?.span.start; let mut items = Vec::new(); loop { items.push(parser(self)?); if let Some(end_spanning) = self.skip(closing)? { return Ok(Spanning::start_end(start_pos, &end_spanning.end(), items)); } } } #[doc(hidden)] pub fn expect_name(&mut self) -> ParseResult<&'a str> { match *self.peek() { Spanning { item: Token::Name(_), .. } => Ok(self.next_token()?.map(|token| { if let Token::Name(name) = token { name } else { panic!("Internal parse error in `expect_name`"); } })), Spanning { item: Token::EndOfFile, .. } => Err(Spanning::new( self.peek().span, ParseError::UnexpectedEndOfFile, )), _ => Err(self.next_token()?.map(ParseError::unexpected_token)), } } } juniper-0.16.2/src/parser/tests/document.rs000064400000000000000000000142311046102023000170000ustar 00000000000000use crate::{ ast::{Arguments, Definition, Field, Operation, OperationType, OwnedDocument, Selection}, graphql_input_value, parser::{document::parse_document_source, ParseError, SourcePosition, Spanning, Token}, schema::model::SchemaType, types::scalars::{EmptyMutation, EmptySubscription}, validation::test_harness::{MutationRoot, QueryRoot, SubscriptionRoot}, value::{DefaultScalarValue, ScalarValue}, }; fn parse_document(s: &str) -> OwnedDocument where S: ScalarValue, { parse_document_source( s, &SchemaType::new::(&(), &(), &()), ) .unwrap_or_else(|_| panic!("Parse error on input {s:#?}")) } fn parse_document_error(s: &str) -> Spanning { match parse_document_source::( s, &SchemaType::new::(&(), &(), &()), ) { Ok(doc) => panic!("*No* parse error on input {s:#?} =>\n{doc:#?}"), Err(err) => err, } } #[test] fn simple_ast() { assert_eq!( parse_document::( r#" { node(id: 4) { id name } } "# ), vec![Definition::Operation(Spanning::start_end( &SourcePosition::new(13, 1, 12), &SourcePosition::new(124, 6, 13), Operation { operation_type: OperationType::Query, name: None, variable_definitions: None, directives: None, selection_set: vec![Selection::Field(Spanning::start_end( &SourcePosition::new(31, 2, 16), &SourcePosition::new(110, 5, 17), Field { alias: None, name: Spanning::start_end( &SourcePosition::new(31, 2, 16), &SourcePosition::new(35, 2, 20), "node", ), arguments: Some(Spanning::start_end( &SourcePosition::new(35, 2, 20), &SourcePosition::new(42, 2, 27), Arguments { items: vec![( Spanning::start_end( &SourcePosition::new(36, 2, 21), &SourcePosition::new(38, 2, 23), "id", ), Spanning::start_end( &SourcePosition::new(40, 2, 25), &SourcePosition::new(41, 2, 26), graphql_input_value!(4), ), )], }, )), directives: None, selection_set: Some(vec![ Selection::Field(Spanning::start_end( &SourcePosition::new(65, 3, 20), &SourcePosition::new(67, 3, 22), Field { alias: None, name: Spanning::start_end( &SourcePosition::new(65, 3, 20), &SourcePosition::new(67, 3, 22), "id", ), arguments: None, directives: None, selection_set: None, }, )), Selection::Field(Spanning::start_end( &SourcePosition::new(88, 4, 20), &SourcePosition::new(92, 4, 24), Field { alias: None, name: Spanning::start_end( &SourcePosition::new(88, 4, 20), &SourcePosition::new(92, 4, 24), "name", ), arguments: None, directives: None, selection_set: None, }, )), ]), }, ))], }, ))] ) } #[test] fn errors() { assert_eq!( parse_document_error::("{"), Spanning::zero_width( &SourcePosition::new(1, 0, 1), ParseError::UnexpectedEndOfFile ) ); assert_eq!( parse_document_error::("{ ...MissingOn }\nfragment MissingOn Type"), Spanning::start_end( &SourcePosition::new(36, 1, 19), &SourcePosition::new(40, 1, 23), ParseError::UnexpectedToken("Type".into()) ) ); assert_eq!( parse_document_error::("{ ...on }"), Spanning::start_end( &SourcePosition::new(8, 0, 8), &SourcePosition::new(9, 0, 9), ParseError::unexpected_token(Token::CurlyClose) ) ); } #[test] fn issue_427_panic_is_not_expected() { struct QueryWithoutFloat; #[crate::graphql_object] impl QueryWithoutFloat { fn echo(value: String) -> String { value } } let schema = >::new::< QueryWithoutFloat, EmptyMutation<()>, EmptySubscription<()>, >(&(), &(), &()); let parse_result = parse_document_source(r##"{ echo(value: 123.0) }"##, &schema); assert_eq!( parse_result.unwrap_err().item, ParseError::ExpectedScalarError("There needs to be a Float type") ); } juniper-0.16.2/src/parser/tests/lexer.rs000064400000000000000000000410471046102023000163060ustar 00000000000000use crate::parser::{Lexer, LexerError, ScalarToken, SourcePosition, Spanning, Token}; fn tokenize_to_vec(s: &str) -> Vec>> { let mut tokens = Vec::new(); let mut lexer = Lexer::new(s); loop { match lexer.next() { Some(Ok(t)) => { let at_eof = t.item == Token::EndOfFile; tokens.push(t); if at_eof { break; } } Some(Err(e)) => panic!("Error in input stream: {e:#?} for {s:#?}"), None => panic!("EOF before EndOfFile token in {s:#?}"), } } tokens } fn tokenize_single(s: &str) -> Spanning> { let mut tokens = tokenize_to_vec(s); assert_eq!(tokens.len(), 2); assert_eq!(tokens[1].item, Token::EndOfFile); tokens.remove(0) } fn tokenize_error(s: &str) -> Spanning { let mut lexer = Lexer::new(s); loop { match lexer.next() { Some(Ok(t)) => { if t.item == Token::EndOfFile { panic!("Tokenizer did not return error for {s:#?}"); } } Some(Err(e)) => { return e; } None => panic!("Tokenizer did not return error for {s:#?}"), } } } #[test] fn empty_source() { assert_eq!( tokenize_to_vec(""), vec![Spanning::zero_width( &SourcePosition::new_origin(), Token::EndOfFile, )] ); } #[test] fn disallow_control_codes() { assert_eq!( Lexer::new("\u{0007}").next(), Some(Err(Spanning::zero_width( &SourcePosition::new_origin(), LexerError::UnknownCharacter('\u{0007}') ))) ); } #[test] fn skip_whitespace() { assert_eq!( tokenize_to_vec( r#" foo "# ), vec![ Spanning::start_end( &SourcePosition::new(14, 2, 12), &SourcePosition::new(17, 2, 15), Token::Name("foo"), ), Spanning::zero_width(&SourcePosition::new(31, 4, 12), Token::EndOfFile), ] ); } #[test] fn skip_comments() { assert_eq!( tokenize_to_vec( r#" #comment foo#comment "# ), vec![ Spanning::start_end( &SourcePosition::new(34, 2, 12), &SourcePosition::new(37, 2, 15), Token::Name("foo"), ), Spanning::zero_width(&SourcePosition::new(58, 3, 12), Token::EndOfFile), ] ); } #[test] fn skip_commas() { assert_eq!( tokenize_to_vec(r#",,,foo,,,"#), vec![ Spanning::start_end( &SourcePosition::new(3, 0, 3), &SourcePosition::new(6, 0, 6), Token::Name("foo"), ), Spanning::zero_width(&SourcePosition::new(9, 0, 9), Token::EndOfFile), ] ); } #[test] fn error_positions() { assert_eq!( Lexer::new( r#" ? "# ) .next(), Some(Err(Spanning::zero_width( &SourcePosition::new(14, 2, 12), LexerError::UnknownCharacter('?') ))) ); } #[test] fn strings() { assert_eq!( tokenize_single(r#""simple""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(8, 0, 8), Token::Scalar(ScalarToken::String("simple")) ) ); assert_eq!( tokenize_single(r#"" white space ""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(15, 0, 15), Token::Scalar(ScalarToken::String(" white space ")) ) ); assert_eq!( tokenize_single(r#""quote \"""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(10, 0, 10), Token::Scalar(ScalarToken::String(r#"quote \""#)) ) ); assert_eq!( tokenize_single(r#""escaped \n\r\b\t\f""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(20, 0, 20), Token::Scalar(ScalarToken::String(r"escaped \n\r\b\t\f")) ) ); assert_eq!( tokenize_single(r#""slashes \\ \/""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(15, 0, 15), Token::Scalar(ScalarToken::String(r"slashes \\ \/")) ) ); assert_eq!( tokenize_single(r#""unicode \u1234\u5678\u90AB\uCDEF""#), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(34, 0, 34), Token::Scalar(ScalarToken::String(r"unicode \u1234\u5678\u90AB\uCDEF")), ) ); } #[test] fn string_errors() { assert_eq!( tokenize_error("\""), Spanning::zero_width( &SourcePosition::new(1, 0, 1), LexerError::UnterminatedString, ) ); assert_eq!( tokenize_error("\"no end quote"), Spanning::zero_width( &SourcePosition::new(13, 0, 13), LexerError::UnterminatedString, ) ); assert_eq!( tokenize_error("\"contains unescaped \u{0007} control char\""), Spanning::zero_width( &SourcePosition::new(20, 0, 20), LexerError::UnknownCharacterInString('\u{0007}'), ) ); assert_eq!( tokenize_error("\"null-byte is not \u{0000} end of file\""), Spanning::zero_width( &SourcePosition::new(18, 0, 18), LexerError::UnknownCharacterInString('\u{0000}'), ) ); assert_eq!( tokenize_error("\"multi\nline\""), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnterminatedString, ) ); assert_eq!( tokenize_error("\"multi\rline\""), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnterminatedString, ) ); assert_eq!( tokenize_error(r#""bad \z esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\z".into()), ) ); assert_eq!( tokenize_error(r#""bad \x esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\x".into()), ) ); assert_eq!( tokenize_error(r#""bad \u1 esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\u1".into()), ) ); assert_eq!( tokenize_error(r#""bad \u0XX1 esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\u0XX1".into()), ) ); assert_eq!( tokenize_error(r#""bad \uXXXX esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\uXXXX".into()), ) ); assert_eq!( tokenize_error(r#""bad \uFXXX esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\uFXXX".into()), ) ); assert_eq!( tokenize_error(r#""bad \uXXXF esc""#), Spanning::zero_width( &SourcePosition::new(6, 0, 6), LexerError::UnknownEscapeSequence("\\uXXXF".into()), ) ); assert_eq!( tokenize_error(r#""unterminated in string \""#), Spanning::zero_width( &SourcePosition::new(26, 0, 26), LexerError::UnterminatedString ) ); assert_eq!( tokenize_error(r#""unterminated \"#), Spanning::zero_width( &SourcePosition::new(15, 0, 15), LexerError::UnterminatedString ) ); // Found by fuzzing. assert_eq!( tokenize_error(r#""\uɠ^A"#), Spanning::zero_width( &SourcePosition::new(5, 0, 5), LexerError::UnterminatedString ) ); } #[test] fn numbers() { fn assert_float_token_eq( source: &str, start: SourcePosition, end: SourcePosition, expected: &str, ) { let parsed = tokenize_single(source); assert_eq!(parsed.span.start, start); assert_eq!(parsed.span.end, end); match parsed.item { Token::Scalar(ScalarToken::Float(actual)) => { assert!( expected == actual, "[expected] {expected} != {actual} [actual]", ); } _ => assert!(false), } } assert_eq!( tokenize_single("4"), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(1, 0, 1), Token::Scalar(ScalarToken::Int("4")) ) ); assert_float_token_eq( "4.123", SourcePosition::new(0, 0, 0), SourcePosition::new(5, 0, 5), "4.123", ); assert_float_token_eq( "4.0", SourcePosition::new(0, 0, 0), SourcePosition::new(3, 0, 3), "4.0", ); assert_eq!( tokenize_single("-4"), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(2, 0, 2), Token::Scalar(ScalarToken::Int("-4")) ) ); assert_eq!( tokenize_single("9"), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(1, 0, 1), Token::Scalar(ScalarToken::Int("9")) ) ); assert_eq!( tokenize_single("0"), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(1, 0, 1), Token::Scalar(ScalarToken::Int("0")) ) ); assert_float_token_eq( "-4.123", SourcePosition::new(0, 0, 0), SourcePosition::new(6, 0, 6), "-4.123", ); assert_float_token_eq( "0.123", SourcePosition::new(0, 0, 0), SourcePosition::new(5, 0, 5), "0.123", ); assert_float_token_eq( "123e4", SourcePosition::new(0, 0, 0), SourcePosition::new(5, 0, 5), "123e4", ); assert_float_token_eq( "123E4", SourcePosition::new(0, 0, 0), SourcePosition::new(5, 0, 5), "123E4", ); assert_float_token_eq( "123e-4", SourcePosition::new(0, 0, 0), SourcePosition::new(6, 0, 6), "123e-4", ); assert_float_token_eq( "123e+4", SourcePosition::new(0, 0, 0), SourcePosition::new(6, 0, 6), "123e+4", ); assert_float_token_eq( "-1.123e4", SourcePosition::new(0, 0, 0), SourcePosition::new(8, 0, 8), "-1.123e4", ); assert_float_token_eq( "-1.123E4", SourcePosition::new(0, 0, 0), SourcePosition::new(8, 0, 8), "-1.123E4", ); assert_float_token_eq( "-1.123e-4", SourcePosition::new(0, 0, 0), SourcePosition::new(9, 0, 9), "-1.123e-4", ); assert_float_token_eq( "-1.123e+4", SourcePosition::new(0, 0, 0), SourcePosition::new(9, 0, 9), "-1.123e+4", ); assert_float_token_eq( "-1.123e45", SourcePosition::new(0, 0, 0), SourcePosition::new(9, 0, 9), "-1.123e45", ); } #[test] fn numbers_errors() { assert_eq!( tokenize_error("00"), Spanning::zero_width( &SourcePosition::new(1, 0, 1), LexerError::UnexpectedCharacter('0') ) ); assert_eq!( tokenize_error("+1"), Spanning::zero_width( &SourcePosition::new(0, 0, 0), LexerError::UnknownCharacter('+') ) ); assert_eq!( tokenize_error("1."), Spanning::zero_width( &SourcePosition::new(2, 0, 2), LexerError::UnexpectedEndOfFile ) ); assert_eq!( tokenize_error(".123"), Spanning::zero_width( &SourcePosition::new(0, 0, 0), LexerError::UnexpectedCharacter('.') ) ); assert_eq!( tokenize_error("1.A"), Spanning::zero_width( &SourcePosition::new(2, 0, 2), LexerError::UnexpectedCharacter('A') ) ); assert_eq!( tokenize_error("-A"), Spanning::zero_width( &SourcePosition::new(1, 0, 1), LexerError::UnexpectedCharacter('A') ) ); assert_eq!( tokenize_error("1.0e"), Spanning::zero_width( &SourcePosition::new(4, 0, 4), LexerError::UnexpectedEndOfFile ) ); assert_eq!( tokenize_error("1.0eA"), Spanning::zero_width( &SourcePosition::new(4, 0, 4), LexerError::UnexpectedCharacter('A') ) ); } #[test] fn punctuation() { assert_eq!( tokenize_single("!"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::ExclamationMark) ); assert_eq!( tokenize_single("$"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::Dollar) ); assert_eq!( tokenize_single("("), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::ParenOpen) ); assert_eq!( tokenize_single(")"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::ParenClose) ); assert_eq!( tokenize_single("..."), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(3, 0, 3), Token::Ellipsis ) ); assert_eq!( tokenize_single(":"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::Colon) ); assert_eq!( tokenize_single("="), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::Equals) ); assert_eq!( tokenize_single("@"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::At) ); assert_eq!( tokenize_single("["), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::BracketOpen) ); assert_eq!( tokenize_single("]"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::BracketClose) ); assert_eq!( tokenize_single("{"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::CurlyOpen) ); assert_eq!( tokenize_single("}"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::CurlyClose) ); assert_eq!( tokenize_single("|"), Spanning::single_width(&SourcePosition::new(0, 0, 0), Token::Pipe) ); } #[test] fn punctuation_error() { assert_eq!( tokenize_error(".."), Spanning::zero_width( &SourcePosition::new(2, 0, 2), LexerError::UnexpectedEndOfFile ) ); assert_eq!( tokenize_error("?"), Spanning::zero_width( &SourcePosition::new(0, 0, 0), LexerError::UnknownCharacter('?') ) ); assert_eq!( tokenize_error("\u{203b}"), Spanning::zero_width( &SourcePosition::new(0, 0, 0), LexerError::UnknownCharacter('\u{203b}') ) ); assert_eq!( tokenize_error("\u{200b}"), Spanning::zero_width( &SourcePosition::new(0, 0, 0), LexerError::UnknownCharacter('\u{200b}') ) ); } #[test] fn display() { for (input, expected) in [ (Token::Name("identifier"), "identifier"), (Token::Scalar(ScalarToken::Int("123")), "123"), (Token::Scalar(ScalarToken::Float("4.5")), "4.5"), ( Token::Scalar(ScalarToken::String("some string")), "\"some string\"", ), ( Token::Scalar(ScalarToken::String("string with \\ escape and \" quote")), "\"string with \\\\ escape and \\\" quote\"", ), (Token::ExclamationMark, "!"), (Token::Dollar, "$"), (Token::ParenOpen, "("), (Token::ParenClose, ")"), (Token::BracketOpen, "["), (Token::BracketClose, "]"), (Token::CurlyOpen, "{"), (Token::CurlyClose, "}"), (Token::Ellipsis, "..."), (Token::Colon, ":"), (Token::Equals, "="), (Token::At, "@"), (Token::Pipe, "|"), ] { assert_eq!(input.to_string(), expected); } } juniper-0.16.2/src/parser/tests/mod.rs000064400000000000000000000000441046102023000157360ustar 00000000000000mod document; mod lexer; mod value; juniper-0.16.2/src/parser/tests/value.rs000064400000000000000000000166731046102023000163120ustar 00000000000000use crate::{ ast::{FromInputValue, InputValue, Type}, graphql_input_value, parser::{value::parse_value_literal, Lexer, Parser, SourcePosition, Spanning}, schema::{ meta::{Argument, EnumMeta, EnumValue, InputObjectMeta, MetaType, ScalarMeta}, model::SchemaType, }, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, ParseScalarValue, ScalarValue}, GraphQLEnum, GraphQLInputObject, IntoFieldError, }; #[derive(GraphQLEnum)] enum Enum { EnumValue, } #[derive(GraphQLInputObject)] struct Bar { foo: String, } #[derive(GraphQLInputObject)] struct Foo { key: i32, other: Bar, } struct Query; #[crate::graphql_object] impl Query { fn int_field() -> i32 { 42 } fn float_field() -> f64 { 3.12 } fn string_field() -> String { "".into() } fn bool_field() -> bool { true } fn enum_field(_foo: Foo) -> Enum { Enum::EnumValue } } fn scalar_meta(name: &'static str) -> MetaType where T: FromInputValue + ParseScalarValue, T::Error: IntoFieldError, { MetaType::Scalar(ScalarMeta::new::(name.into())) } fn parse_value(s: &str, meta: &MetaType) -> Spanning> where S: ScalarValue, { let mut lexer = Lexer::new(s); let mut parser = Parser::new(&mut lexer).unwrap_or_else(|_| panic!("Lexer error on input {s:#?}")); let schema = SchemaType::new::, EmptySubscription<()>>(&(), &(), &()); parse_value_literal(&mut parser, false, &schema, Some(meta)) .unwrap_or_else(|_| panic!("Parse error on input {s:#?}")) } #[test] fn input_value_literals() { assert_eq!( parse_value::("123", &scalar_meta::("Int")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(3, 0, 3), graphql_input_value!(123), ), ); assert_eq!( parse_value::("123.45", &scalar_meta::("Float")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(6, 0, 6), graphql_input_value!(123.45), ), ); assert_eq!( parse_value::("true", &scalar_meta::("Bool")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(4, 0, 4), graphql_input_value!(true), ), ); assert_eq!( parse_value::("false", &scalar_meta::("Bool")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(5, 0, 5), graphql_input_value!(false), ), ); assert_eq!( parse_value::(r#""test""#, &scalar_meta::("String")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(6, 0, 6), graphql_input_value!("test"), ), ); let values = &[EnumValue::new("enum_value")]; let e: EnumMeta = EnumMeta::new::("TestEnum".into(), values); assert_eq!( parse_value::("enum_value", &MetaType::Enum(e)), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(10, 0, 10), graphql_input_value!(enum_value), ), ); assert_eq!( parse_value::("$variable", &scalar_meta::("Int")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(9, 0, 9), graphql_input_value!(@variable), ), ); assert_eq!( parse_value::("[]", &scalar_meta::("Int")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(2, 0, 2), graphql_input_value!([]), ), ); assert_eq!( parse_value::("[1, [2, 3]]", &scalar_meta::("Int")), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(11, 0, 11), InputValue::parsed_list(vec![ Spanning::start_end( &SourcePosition::new(1, 0, 1), &SourcePosition::new(2, 0, 2), graphql_input_value!(1), ), Spanning::start_end( &SourcePosition::new(4, 0, 4), &SourcePosition::new(10, 0, 10), InputValue::parsed_list(vec![ Spanning::start_end( &SourcePosition::new(5, 0, 5), &SourcePosition::new(6, 0, 6), graphql_input_value!(2), ), Spanning::start_end( &SourcePosition::new(8, 0, 8), &SourcePosition::new(9, 0, 9), graphql_input_value!(3), ), ]), ), ]), ), ); let fields = [ Argument::new("key", Type::NonNullNamed("Int".into())), Argument::new("other", Type::NonNullNamed("Bar".into())), ]; let meta = &MetaType::InputObject(InputObjectMeta::new::("foo".into(), &fields)); assert_eq!( parse_value::("{}", meta), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(2, 0, 2), graphql_input_value!({}), ), ); assert_eq!( parse_value::(r#"{key: 123, other: {foo: "bar"}}"#, meta), Spanning::start_end( &SourcePosition::new(0, 0, 0), &SourcePosition::new(31, 0, 31), InputValue::parsed_object(vec![ ( Spanning::start_end( &SourcePosition::new(1, 0, 1), &SourcePosition::new(4, 0, 4), "key".to_owned(), ), Spanning::start_end( &SourcePosition::new(6, 0, 6), &SourcePosition::new(9, 0, 9), graphql_input_value!(123), ), ), ( Spanning::start_end( &SourcePosition::new(11, 0, 11), &SourcePosition::new(16, 0, 16), "other".to_owned(), ), Spanning::start_end( &SourcePosition::new(18, 0, 18), &SourcePosition::new(30, 0, 30), InputValue::parsed_object(vec![( Spanning::start_end( &SourcePosition::new(19, 0, 19), &SourcePosition::new(22, 0, 22), "foo".to_owned(), ), Spanning::start_end( &SourcePosition::new(24, 0, 24), &SourcePosition::new(29, 0, 29), graphql_input_value!("bar"), ), )]), ), ), ]), ), ); } juniper-0.16.2/src/parser/utils.rs000064400000000000000000000123401046102023000151570ustar 00000000000000use std::fmt; /// A reference to a line and column in an input source file #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] pub struct SourcePosition { index: usize, line: usize, col: usize, } /// Range of characters in the input source, starting at the character pointed by the `start` field /// and ending just before the `end` marker. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Span { /// Start position of this [`Span`]. pub start: SourcePosition, /// End position of this [`Span`]. /// /// > __NOTE__: This points to the first source position __after__ this [`Span`]. pub end: SourcePosition, } impl Span { #[doc(hidden)] #[inline] pub fn zero_width(pos: SourcePosition) -> Self { Self { start: pos, end: pos, } } #[doc(hidden)] #[inline] pub fn single_width(pos: SourcePosition) -> Self { let mut end = pos; end.advance_col(); Self { start: pos, end } } #[doc(hidden)] #[inline] pub fn unlocated() -> Self { Self { start: SourcePosition::new_origin(), end: SourcePosition::new_origin(), } } } /// Data structure used to wrap items into a [`Span`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Spanning { /// Wrapped item. pub item: T, /// [`Span`] of the wrapped item. pub span: Sp, } impl Spanning { #[doc(hidden)] pub fn new(span: Span, item: T) -> Self { Self { item, span } } #[doc(hidden)] pub fn zero_width(&pos: &SourcePosition, item: T) -> Spanning { Self::new(Span::zero_width(pos), item) } #[doc(hidden)] pub fn single_width(&pos: &SourcePosition, item: T) -> Spanning { Self::new(Span::single_width(pos), item) } #[doc(hidden)] pub fn start_end(&start: &SourcePosition, &end: &SourcePosition, item: T) -> Spanning { Self::new(Span { start, end }, item) } #[doc(hidden)] #[allow(clippy::self_named_constructors)] pub fn spanning(v: Vec>) -> Option>>> { if let (Some(start), Some(end)) = (v.first().map(|s| s.span), v.last().map(|s| s.span)) { Some(Spanning::new( Span { start: start.start, end: end.end, }, v, )) } else { None } } #[doc(hidden)] pub fn unlocated(item: T) -> Spanning { Self::new(Span::unlocated(), item) } /// Returns start position of the item. #[inline] pub fn start(&self) -> SourcePosition { self.span.start } /// Returns end position of the item. /// /// > __NOTE__: This points to the first source position __after__ the item. #[inline] pub fn end(&self) -> SourcePosition { self.span.end } /// Modify the contents of the spanned item. pub fn map O>(self, f: F) -> Spanning { Spanning::new(self.span, f(self.item)) } /// Modifies the contents of the spanned item in case `f` returns [`Some`], /// or returns [`None`] otherwise. pub fn and_then Option>(self, f: F) -> Option> { f(self.item).map(|item| Spanning::new(self.span, item)) } /// Converts into a [`Spanning`] containing a borrowed item and a borrowed [`Span`]. pub(crate) fn as_ref(&self) -> Spanning<&'_ T, &'_ Span> { Spanning { item: &self.item, span: &self.span, } } } impl fmt::Display for Spanning { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}. At {}", self.item, self.span.start) } } impl std::error::Error for Spanning {} impl SourcePosition { #[doc(hidden)] pub fn new(index: usize, line: usize, col: usize) -> SourcePosition { assert!(index >= line + col); SourcePosition { index, line, col } } #[doc(hidden)] pub fn new_origin() -> SourcePosition { SourcePosition { index: 0, line: 0, col: 0, } } #[doc(hidden)] pub fn advance_col(&mut self) { self.index += 1; self.col += 1; } #[doc(hidden)] pub fn advance_line(&mut self) { self.index += 1; self.line += 1; self.col = 0; } /// The index of the character in the input source /// /// Zero-based index. Take a substring of the original source starting at /// this index to access the item pointed to by this `SourcePosition`. pub fn index(&self) -> usize { self.index } /// The line of the character in the input source /// /// Zero-based index: the first line is line zero. pub fn line(&self) -> usize { self.line } /// The column of the character in the input source /// /// Zero-based index: the first column is column zero. pub fn column(&self) -> usize { self.col } } impl fmt::Display for SourcePosition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{}", self.line, self.col) } } juniper-0.16.2/src/parser/value.rs000064400000000000000000000147121046102023000151400ustar 00000000000000use crate::ast::InputValue; use crate::{ parser::{ParseError, ParseResult, Parser, ScalarToken, Spanning, Token}, schema::{ meta::{InputObjectMeta, MetaType}, model::SchemaType, }, value::ScalarValue, }; use super::utils::Span; pub fn parse_value_literal<'b, S>( parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, tpe: Option<&MetaType<'b, S>>, ) -> ParseResult> where S: ScalarValue, { match (parser.peek(), tpe) { ( &Spanning { item: Token::BracketOpen, .. }, _, ) => parse_list_literal(parser, is_const, schema, tpe), ( &Spanning { item: Token::CurlyOpen, .. }, None, ) => parse_object_literal(parser, is_const, schema, None), ( &Spanning { item: Token::CurlyOpen, .. }, Some(MetaType::InputObject(o)), ) => parse_object_literal(parser, is_const, schema, Some(o)), ( &Spanning { item: Token::Dollar, .. }, _, ) if !is_const => parse_variable_literal(parser), ( &Spanning { item: Token::Scalar(_), .. }, Some(MetaType::Scalar(s)), ) => { if let Spanning { item: Token::Scalar(scalar), span, } = parser.next_token()? { (s.parse_fn)(scalar) .map(|s| Spanning::new(span, InputValue::Scalar(s))) .or_else(|_| parse_scalar_literal_by_infered_type(scalar, span, schema)) } else { unreachable!() } } ( &Spanning { item: Token::Scalar(_), .. }, _, ) => { if let Spanning { item: Token::Scalar(token), span, } = parser.next_token()? { parse_scalar_literal_by_infered_type(token, span, schema) } else { unreachable!() } } ( &Spanning { item: Token::Name("true"), .. }, _, ) => Ok(parser.next_token()?.map(|_| InputValue::scalar(true))), ( &Spanning { item: Token::Name("false"), .. }, _, ) => Ok(parser.next_token()?.map(|_| InputValue::scalar(false))), ( &Spanning { item: Token::Name("null"), .. }, _, ) => Ok(parser.next_token()?.map(|_| InputValue::null())), ( &Spanning { item: Token::Name(name), .. }, _, ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), _ => Err(parser.next_token()?.map(ParseError::unexpected_token)), } } fn parse_list_literal<'b, S>( parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, tpe: Option<&MetaType<'b, S>>, ) -> ParseResult> where S: ScalarValue, { Ok(parser .delimited_list( &Token::BracketOpen, |p| parse_value_literal(p, is_const, schema, tpe), &Token::BracketClose, )? .map(InputValue::parsed_list)) } fn parse_object_literal<'b, S>( parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, object_tpe: Option<&InputObjectMeta<'b, S>>, ) -> ParseResult> where S: ScalarValue, { Ok(parser .delimited_list( &Token::CurlyOpen, |p| parse_object_field(p, is_const, schema, object_tpe), &Token::CurlyClose, )? .map(|items| InputValue::parsed_object(items.into_iter().map(|s| s.item).collect()))) } fn parse_object_field<'b, S>( parser: &mut Parser<'_>, is_const: bool, schema: &'b SchemaType<'b, S>, object_meta: Option<&InputObjectMeta<'b, S>>, ) -> ParseResult<(Spanning, Spanning>)> where S: ScalarValue, { let key = parser.expect_name()?; let tpe = object_meta .and_then(|o| o.input_fields.iter().find(|f| f.name == key.item)) .and_then(|f| schema.lookup_type(&f.arg_type)); parser.expect(&Token::Colon)?; let value = parse_value_literal(parser, is_const, schema, tpe)?; Ok(Spanning::start_end( &key.span.start, &value.span.end.clone(), (key.map(|s| s.to_owned()), value), )) } fn parse_variable_literal(parser: &mut Parser<'_>) -> ParseResult> where S: ScalarValue, { let start_pos = &parser.expect(&Token::Dollar)?.span.start; let Spanning { item: name, span: end_span, .. } = parser.expect_name()?; Ok(Spanning::start_end( start_pos, &end_span.end, InputValue::variable(name), )) } fn parse_scalar_literal_by_infered_type<'b, S>( token: ScalarToken<'_>, span: Span, schema: &'b SchemaType<'b, S>, ) -> ParseResult> where S: ScalarValue, { let result = match token { ScalarToken::String(_) => { if let Some(MetaType::Scalar(s)) = schema.concrete_type_by_name("String") { (s.parse_fn)(token).map(InputValue::Scalar) } else { Err(ParseError::ExpectedScalarError( "There needs to be a String type", )) } } ScalarToken::Int(_) => { if let Some(MetaType::Scalar(s)) = schema.concrete_type_by_name("Int") { (s.parse_fn)(token).map(InputValue::Scalar) } else { Err(ParseError::ExpectedScalarError( "There needs to be an Int type", )) } } ScalarToken::Float(_) => { if let Some(MetaType::Scalar(s)) = schema.concrete_type_by_name("Float") { (s.parse_fn)(token).map(InputValue::Scalar) } else { Err(ParseError::ExpectedScalarError( "There needs to be a Float type", )) } } }; result .map(|s| Spanning::new(span, s)) .map_err(|e| Spanning::new(span, e)) } juniper-0.16.2/src/schema/meta.rs000064400000000000000000000605151046102023000147200ustar 00000000000000//! Types used to describe a `GraphQL` schema use juniper::IntoFieldError; use std::{borrow::Cow, fmt}; use crate::{ ast::{FromInputValue, InputValue, Type}, parser::{ParseError, ScalarToken}, schema::model::SchemaType, types::base::TypeKind, value::{DefaultScalarValue, ParseScalarValue}, FieldError, }; /// Whether an item is deprecated, with context. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum DeprecationStatus { /// The field/variant is not deprecated. Current, /// The field/variant is deprecated, with an optional reason Deprecated(Option), } impl DeprecationStatus { /// If this deprecation status indicates the item is deprecated. pub fn is_deprecated(&self) -> bool { match self { DeprecationStatus::Current => false, DeprecationStatus::Deprecated(_) => true, } } /// An optional reason for the deprecation, or none if `Current`. pub fn reason(&self) -> Option<&str> { match self { DeprecationStatus::Current => None, DeprecationStatus::Deprecated(rsn) => rsn.as_deref(), } } } /// Scalar type metadata pub struct ScalarMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub specified_by_url: Option>, pub(crate) try_parse_fn: InputValueParseFn, pub(crate) parse_fn: ScalarTokenParseFn, } /// Shortcut for an [`InputValue`] parsing function. pub type InputValueParseFn = for<'b> fn(&'b InputValue) -> Result<(), FieldError>; /// Shortcut for a [`ScalarToken`] parsing function. pub type ScalarTokenParseFn = for<'b> fn(ScalarToken<'b>) -> Result; /// List type metadata #[derive(Debug)] pub struct ListMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, #[doc(hidden)] pub expected_size: Option, } /// Nullable type metadata #[derive(Debug)] pub struct NullableMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, } /// Object type metadata #[derive(Debug)] pub struct ObjectMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub fields: Vec>, #[doc(hidden)] pub interface_names: Vec, } /// Enum type metadata pub struct EnumMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub values: Vec, pub(crate) try_parse_fn: InputValueParseFn, } /// Interface type metadata #[derive(Debug)] pub struct InterfaceMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub fields: Vec>, #[doc(hidden)] pub interface_names: Vec, } /// Union type metadata #[derive(Debug)] pub struct UnionMeta<'a> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub of_type_names: Vec, } /// Input object metadata pub struct InputObjectMeta<'a, S> { #[doc(hidden)] pub name: Cow<'a, str>, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub input_fields: Vec>, pub(crate) try_parse_fn: InputValueParseFn, } /// A placeholder for not-yet-registered types /// /// After a type's `meta` method has been called but before it has returned, a placeholder type /// is inserted into a registry to indicate existence. #[derive(Debug)] pub struct PlaceholderMeta<'a> { #[doc(hidden)] pub of_type: Type<'a>, } /// Generic type metadata #[derive(Debug)] pub enum MetaType<'a, S = DefaultScalarValue> { #[doc(hidden)] Scalar(ScalarMeta<'a, S>), #[doc(hidden)] List(ListMeta<'a>), #[doc(hidden)] Nullable(NullableMeta<'a>), #[doc(hidden)] Object(ObjectMeta<'a, S>), #[doc(hidden)] Enum(EnumMeta<'a, S>), #[doc(hidden)] Interface(InterfaceMeta<'a, S>), #[doc(hidden)] Union(UnionMeta<'a>), #[doc(hidden)] InputObject(InputObjectMeta<'a, S>), #[doc(hidden)] Placeholder(PlaceholderMeta<'a>), } /// Metadata for a field #[derive(Debug, Clone)] pub struct Field<'a, S> { #[doc(hidden)] pub name: smartstring::alias::String, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub arguments: Option>>, #[doc(hidden)] pub field_type: Type<'a>, #[doc(hidden)] pub deprecation_status: DeprecationStatus, } impl<'a, S> Field<'a, S> { /// Returns true if the type is built-in to GraphQL. pub fn is_builtin(&self) -> bool { // "used exclusively by GraphQL’s introspection system" self.name.starts_with("__") } } /// Metadata for an argument to a field #[derive(Debug, Clone)] pub struct Argument<'a, S> { #[doc(hidden)] pub name: String, #[doc(hidden)] pub description: Option, #[doc(hidden)] pub arg_type: Type<'a>, #[doc(hidden)] pub default_value: Option>, } impl<'a, S> Argument<'a, S> { /// Returns true if the type is built-in to GraphQL. pub fn is_builtin(&self) -> bool { // "used exclusively by GraphQL’s introspection system" self.name.starts_with("__") } } /// Metadata for a single value in an enum #[derive(Debug, Clone)] pub struct EnumValue { /// The name of the enum value /// /// This is the string literal representation of the enum in responses. pub name: String, /// The optional description of the enum value. /// /// Note: this is not the description of the enum itself; it's the /// description of this enum _value_. pub description: Option, /// Whether the field is deprecated or not, with an optional reason. pub deprecation_status: DeprecationStatus, } impl<'a, S> MetaType<'a, S> { /// Access the name of the type, if applicable /// /// Lists, non-null wrappers, and placeholders don't have names. pub fn name(&self) -> Option<&str> { match *self { MetaType::Scalar(ScalarMeta { ref name, .. }) | MetaType::Object(ObjectMeta { ref name, .. }) | MetaType::Enum(EnumMeta { ref name, .. }) | MetaType::Interface(InterfaceMeta { ref name, .. }) | MetaType::Union(UnionMeta { ref name, .. }) | MetaType::InputObject(InputObjectMeta { ref name, .. }) => Some(name), _ => None, } } /// Access the description of the type, if applicable /// /// Lists, nullable wrappers, and placeholders don't have names. pub fn description(&self) -> Option<&str> { match self { MetaType::Scalar(ScalarMeta { description, .. }) | MetaType::Object(ObjectMeta { description, .. }) | MetaType::Enum(EnumMeta { description, .. }) | MetaType::Interface(InterfaceMeta { description, .. }) | MetaType::Union(UnionMeta { description, .. }) | MetaType::InputObject(InputObjectMeta { description, .. }) => description.as_deref(), _ => None, } } /// Accesses the [specification URL][0], if applicable. /// /// Only custom GraphQL scalars can have a [specification URL][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--specifiedBy pub fn specified_by_url(&self) -> Option<&str> { match self { Self::Scalar(ScalarMeta { specified_by_url, .. }) => specified_by_url.as_deref(), _ => None, } } /// Construct a `TypeKind` for a given type /// /// # Panics /// /// Panics if the type represents a placeholder or nullable type. pub fn type_kind(&self) -> TypeKind { match *self { MetaType::Scalar(_) => TypeKind::Scalar, MetaType::List(_) => TypeKind::List, MetaType::Nullable(_) => panic!("Can't take type_kind of nullable meta type"), MetaType::Object(_) => TypeKind::Object, MetaType::Enum(_) => TypeKind::Enum, MetaType::Interface(_) => TypeKind::Interface, MetaType::Union(_) => TypeKind::Union, MetaType::InputObject(_) => TypeKind::InputObject, MetaType::Placeholder(_) => panic!("Can't take type_kind of placeholder meta type"), } } /// Access a field's meta data given its name /// /// Only objects and interfaces have fields. This method always returns `None` for other types. pub fn field_by_name(&self, name: &str) -> Option<&Field> { match *self { MetaType::Object(ObjectMeta { ref fields, .. }) | MetaType::Interface(InterfaceMeta { ref fields, .. }) => { fields.iter().find(|f| f.name == name) } _ => None, } } /// Access an input field's meta data given its name /// /// Only input objects have input fields. This method always returns `None` for other types. pub fn input_field_by_name(&self, name: &str) -> Option<&Argument> { match *self { MetaType::InputObject(InputObjectMeta { ref input_fields, .. }) => input_fields.iter().find(|f| f.name == name), _ => None, } } /// Construct a `Type` literal instance based on the metadata pub fn as_type(&self) -> Type<'a> { match *self { MetaType::Scalar(ScalarMeta { ref name, .. }) | MetaType::Object(ObjectMeta { ref name, .. }) | MetaType::Enum(EnumMeta { ref name, .. }) | MetaType::Interface(InterfaceMeta { ref name, .. }) | MetaType::Union(UnionMeta { ref name, .. }) | MetaType::InputObject(InputObjectMeta { ref name, .. }) => { Type::NonNullNamed(name.clone()) } MetaType::List(ListMeta { ref of_type, expected_size, }) => Type::NonNullList(Box::new(of_type.clone()), expected_size), MetaType::Nullable(NullableMeta { ref of_type }) => match *of_type { Type::NonNullNamed(ref inner) => Type::Named(inner.clone()), Type::NonNullList(ref inner, expected_size) => { Type::List(inner.clone(), expected_size) } ref t => t.clone(), }, MetaType::Placeholder(PlaceholderMeta { ref of_type }) => of_type.clone(), } } /// Access the input value parse function, if applicable /// /// An input value parse function is a function that takes an `InputValue` instance and returns /// `true` if it can be parsed as the provided type. /// /// Only scalars, enums, and input objects have parse functions. pub fn input_value_parse_fn(&self) -> Option> { match *self { MetaType::Scalar(ScalarMeta { ref try_parse_fn, .. }) | MetaType::Enum(EnumMeta { ref try_parse_fn, .. }) | MetaType::InputObject(InputObjectMeta { ref try_parse_fn, .. }) => Some(*try_parse_fn), _ => None, } } /// Returns true if the type is a composite type /// /// Objects, interfaces, and unions are composite. pub fn is_composite(&self) -> bool { matches!( *self, MetaType::Object(_) | MetaType::Interface(_) | MetaType::Union(_) ) } /// Returns true if the type can occur in leaf positions in queries /// /// Only enums and scalars are leaf types. pub fn is_leaf(&self) -> bool { matches!(*self, MetaType::Enum(_) | MetaType::Scalar(_)) } /// Returns true if the type is abstract /// /// Only interfaces and unions are abstract types. pub fn is_abstract(&self) -> bool { matches!(*self, MetaType::Interface(_) | MetaType::Union(_)) } /// Returns true if the type can be used in input positions, e.g. arguments or variables /// /// Only scalars, enums, and input objects are input types. pub fn is_input(&self) -> bool { matches!( *self, MetaType::Scalar(_) | MetaType::Enum(_) | MetaType::InputObject(_) ) } /// Returns true if the type is built-in to GraphQL. pub fn is_builtin(&self) -> bool { if let Some(name) = self.name() { // "used exclusively by GraphQL’s introspection system" { name.starts_with("__") || // https://spec.graphql.org/October2021#sec-Scalars name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" || // Our custom empty markers name == "_EmptyMutation" || name == "_EmptySubscription" } } else { false } } pub(crate) fn fields<'b>(&self, schema: &'b SchemaType) -> Option>> { schema .lookup_type(&self.as_type()) .and_then(|tpe| match *tpe { MetaType::Interface(ref i) => Some(i.fields.iter().collect()), MetaType::Object(ref o) => Some(o.fields.iter().collect()), MetaType::Union(ref u) => Some( u.of_type_names .iter() .filter_map(|n| schema.concrete_type_by_name(n)) .filter_map(|t| t.fields(schema)) .flatten() .collect(), ), _ => None, }) } } impl<'a, S> ScalarMeta<'a, S> { /// Builds a new [`ScalarMeta`] type with the specified `name`. pub fn new(name: Cow<'a, str>) -> Self where T: FromInputValue + ParseScalarValue, T::Error: IntoFieldError, { Self { name, description: None, specified_by_url: None, try_parse_fn: try_parse_fn::, parse_fn: >::from_str, } } /// Sets the `description` of this [`ScalarMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Sets the [specification URL][0] for this [`ScalarMeta`] type. /// /// Overwrites any previously set [specification URL][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--specifiedBy #[must_use] pub fn specified_by_url(mut self, url: impl Into>) -> Self { self.specified_by_url = Some(url.into()); self } /// Wraps this [`ScalarMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Scalar(self) } } impl<'a> ListMeta<'a> { /// Build a new [`ListMeta`] type by wrapping the specified [`Type`]. /// /// Specifying `expected_size` will be used to ensure that values of this /// type will always match it. pub fn new(of_type: Type<'a>, expected_size: Option) -> Self { Self { of_type, expected_size, } } /// Wraps this [`ListMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::List(self) } } impl<'a> NullableMeta<'a> { /// Build a new [`NullableMeta`] type by wrapping the specified [`Type`]. pub fn new(of_type: Type<'a>) -> Self { Self { of_type } } /// Wraps this [`NullableMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Nullable(self) } } impl<'a, S> ObjectMeta<'a, S> { /// Build a new [`ObjectMeta`] type with the specified `name` and `fields`. pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> Self where S: Clone, { Self { name, description: None, fields: fields.to_vec(), interface_names: vec![], } } /// Sets the `description` of this [`ObjectMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Set the `interfaces` this [`ObjectMeta`] type 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 } /// Wraps this [`ObjectMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Object(self) } } impl<'a, S> EnumMeta<'a, S> { /// Build a new [`EnumMeta`] type with the specified `name` and possible /// `values`. pub fn new(name: Cow<'a, str>, values: &[EnumValue]) -> Self where T: FromInputValue, T::Error: IntoFieldError, { Self { name, description: None, values: values.to_owned(), try_parse_fn: try_parse_fn::, } } /// Sets the `description` of this [`EnumMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Wraps this [`EnumMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Enum(self) } } impl<'a, S> InterfaceMeta<'a, S> { /// Builds a new [`InterfaceMeta`] type with the specified `name` and /// `fields`. pub fn new(name: Cow<'a, str>, fields: &[Field<'a, S>]) -> Self where S: Clone, { Self { name, description: None, fields: fields.to_vec(), interface_names: Vec::new(), } } /// Sets the `description` of this [`InterfaceMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { 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 } /// Wraps this [`InterfaceMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Interface(self) } } impl<'a> UnionMeta<'a> { /// Build a new [`UnionMeta`] type with the specified `name` and possible /// [`Type`]s. pub fn new(name: Cow<'a, str>, of_types: &[Type]) -> Self { Self { name, description: None, of_type_names: of_types.iter().map(|t| t.innermost_name().into()).collect(), } } /// Sets the `description` of this [`UnionMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Wraps this [`UnionMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Union(self) } } impl<'a, S> InputObjectMeta<'a, S> { /// Builds a new [`InputObjectMeta`] type with the specified `name` and /// `input_fields`. pub fn new(name: Cow<'a, str>, input_fields: &[Argument<'a, S>]) -> Self where T: FromInputValue, T::Error: IntoFieldError, S: Clone, { Self { name, description: None, input_fields: input_fields.to_vec(), try_parse_fn: try_parse_fn::, } } /// Set the `description` of this [`InputObjectMeta`] type. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Wraps this [`InputObjectMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::InputObject(self) } } impl<'a, S> Field<'a, S> { /// Set the `description` of this [`Field`]. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Adds an `argument` to this [`Field`]. /// /// Arguments are unordered and can't contain duplicates by name. #[must_use] pub fn argument(mut self, argument: Argument<'a, S>) -> Self { match self.arguments { None => { self.arguments = Some(vec![argument]); } Some(ref mut args) => { args.push(argument); } }; self } /// Sets this [`Field`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into)); self } } impl<'a, S> Argument<'a, S> { /// Builds a new [`Argument`] of the given [`Type`] with the given `name`. pub fn new(name: &str, arg_type: Type<'a>) -> Self { Self { name: name.into(), description: None, arg_type, default_value: None, } } /// Sets the `description` of this [`Argument`]. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Set the default value of this [`Argument`]. /// /// Overwrites any previously set default value. #[must_use] pub fn default_value(mut self, val: InputValue) -> Self { self.default_value = Some(val); self } } impl EnumValue { /// Constructs a new [`EnumValue`] with the provided `name`. pub fn new(name: &str) -> Self { Self { name: name.into(), description: None, deprecation_status: DeprecationStatus::Current, } } /// Sets the `description` of this [`EnumValue`]. /// /// Overwrites any previously set description. #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.into()); self } /// Sets this [`EnumValue`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into)); self } } impl<'a, S: fmt::Debug> fmt::Debug for ScalarMeta<'a, S> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("ScalarMeta") .field("name", &self.name) .field("description", &self.description) .finish() } } impl<'a, S: fmt::Debug> fmt::Debug for EnumMeta<'a, S> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("EnumMeta") .field("name", &self.name) .field("description", &self.description) .field("values", &self.values) .finish() } } impl<'a, S: fmt::Debug> fmt::Debug for InputObjectMeta<'a, S> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("InputObjectMeta") .field("name", &self.name) .field("description", &self.description) .field("input_fields", &self.input_fields) .finish() } } fn try_parse_fn(v: &InputValue) -> Result<(), FieldError> where T: FromInputValue, T::Error: IntoFieldError, { T::from_input_value(v) .map(drop) .map_err(T::Error::into_field_error) } juniper-0.16.2/src/schema/mod.rs000064400000000000000000000001451046102023000145420ustar 00000000000000#![allow(clippy::module_inception)] pub mod meta; pub mod model; pub mod schema; pub mod translate; juniper-0.16.2/src/schema/model.rs000064400000000000000000000712131046102023000150670ustar 00000000000000use std::{borrow::Cow, fmt}; use fnv::FnvHashMap; #[cfg(feature = "schema-language")] use graphql_parser::schema::Document; use crate::{ ast::Type, executor::{Context, Registry}, schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta}, types::{base::GraphQLType, name::Name}, value::{DefaultScalarValue, ScalarValue}, GraphQLEnum, }; /// Root query node of a schema /// /// This brings the mutation, subscription and query types together, /// and provides the predefined metadata fields. #[derive(Debug)] pub struct RootNode< 'a, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, S = DefaultScalarValue, > where S: ScalarValue, { #[doc(hidden)] pub query_type: QueryT, #[doc(hidden)] pub query_info: QueryT::TypeInfo, #[doc(hidden)] pub mutation_type: MutationT, #[doc(hidden)] pub mutation_info: MutationT::TypeInfo, #[doc(hidden)] pub subscription_type: SubscriptionT, #[doc(hidden)] pub subscription_info: SubscriptionT::TypeInfo, #[doc(hidden)] pub schema: SchemaType<'a, S>, #[doc(hidden)] pub introspection_disabled: bool, } /// Metadata for a schema #[derive(Debug)] pub struct SchemaType<'a, S> { pub(crate) description: Option>, pub(crate) types: FnvHashMap>, pub(crate) query_type_name: String, pub(crate) mutation_type_name: Option, pub(crate) subscription_type_name: Option, directives: FnvHashMap>, } impl<'a, S> Context for SchemaType<'a, S> {} #[derive(Clone)] pub enum TypeType<'a, S: 'a> { Concrete(&'a MetaType<'a, S>), NonNull(Box>), List(Box>, Option), } #[derive(Debug)] pub struct DirectiveType<'a, S> { pub name: String, pub description: Option, pub locations: Vec, pub arguments: Vec>, pub is_repeatable: bool, } #[derive(Clone, PartialEq, Eq, Debug, GraphQLEnum)] #[graphql(name = "__DirectiveLocation", internal)] pub enum DirectiveLocation { Query, Mutation, Subscription, Field, Scalar, #[graphql(name = "FRAGMENT_DEFINITION")] FragmentDefinition, #[graphql(name = "FIELD_DEFINITION")] FieldDefinition, #[graphql(name = "VARIABLE_DEFINITION")] VariableDefinition, #[graphql(name = "FRAGMENT_SPREAD")] FragmentSpread, #[graphql(name = "INLINE_FRAGMENT")] InlineFragment, #[graphql(name = "ENUM_VALUE")] EnumValue, } impl<'a, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, DefaultScalarValue> where QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes, /// parametrizing it with a [`DefaultScalarValue`]. pub fn new(query: QueryT, mutation: MutationT, subscription: SubscriptionT) -> Self { Self::new_with_info(query, mutation, subscription, (), (), ()) } } impl<'a, QueryT, MutationT, SubscriptionT, S> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where S: ScalarValue + 'a, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes, /// parametrizing it with the provided [`ScalarValue`]. pub fn new_with_scalar_value( query: QueryT, mutation: MutationT, subscription: SubscriptionT, ) -> Self { RootNode::new_with_info(query, mutation, subscription, (), (), ()) } } impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, S: ScalarValue + 'a, { /// Construct a new root node from query and mutation nodes, /// while also providing type info objects for the query and /// mutation types. pub fn new_with_info( query_obj: QueryT, mutation_obj: MutationT, subscription_obj: SubscriptionT, query_info: QueryT::TypeInfo, mutation_info: MutationT::TypeInfo, subscription_info: SubscriptionT::TypeInfo, ) -> Self { Self { query_type: query_obj, mutation_type: mutation_obj, subscription_type: subscription_obj, schema: SchemaType::new::( &query_info, &mutation_info, &subscription_info, ), query_info, mutation_info, subscription_info, introspection_disabled: false, } } /// Disables introspection for this [`RootNode`], making it to return a [`FieldError`] whenever /// its `__schema` or `__type` field is resolved. /// /// By default, all introspection queries are allowed. /// /// # Example /// /// ```rust /// # use juniper::{ /// # graphql_object, graphql_vars, EmptyMutation, EmptySubscription, GraphQLError, /// # RootNode, /// # }; /// # /// pub struct Query; /// /// #[graphql_object] /// impl Query { /// fn some() -> bool { /// true /// } /// } /// /// type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; /// /// let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()) /// .disable_introspection(); /// /// # // language=GraphQL /// let query = "query { __schema { queryType { name } } }"; /// /// match juniper::execute_sync(query, None, &schema, &graphql_vars! {}, &()) { /// Err(GraphQLError::ValidationError(errs)) => { /// assert_eq!( /// errs.first().unwrap().message(), /// "GraphQL introspection is not allowed, but the operation contained `__schema`", /// ); /// } /// res => panic!("expected `ValidationError`, returned: {res:#?}"), /// } /// ``` pub fn disable_introspection(mut self) -> Self { self.introspection_disabled = true; self } /// Enables introspection for this [`RootNode`], if it was previously [disabled][1]. /// /// By default, all introspection queries are allowed. /// /// [1]: RootNode::disable_introspection pub fn enable_introspection(mut self) -> Self { self.introspection_disabled = false; self } #[cfg(feature = "schema-language")] /// Returns this [`RootNode`] as a [`String`] containing the schema in [SDL (schema definition language)]. /// /// # Sorted /// /// The order of the generated definitions is stable and is sorted in the "type-then-name" manner. /// /// If another sorting order is required, then the [`as_document()`] method should be used, which allows to sort the /// returned [`Document`] in the desired manner and then to convert it [`to_string()`]. /// /// [`as_document()`]: RootNode::as_document /// [`to_string()`]: ToString::to_string /// [0]: https://graphql.org/learn/schema#type-language #[must_use] pub fn as_sdl(&self) -> String { use crate::schema::translate::graphql_parser::sort_schema_document; let mut doc = self.as_document(); sort_schema_document(&mut doc); doc.to_string() } #[cfg(feature = "schema-language")] /// Returns this [`RootNode`] as a [`graphql_parser`]'s [`Document`]. /// /// # Unsorted /// /// The order of the generated definitions in the returned [`Document`] is NOT stable and may change without any /// real schema changes. #[must_use] pub fn as_document(&'a self) -> Document<'a, &'a str> { use crate::schema::translate::{ graphql_parser::GraphQLParserTranslator, SchemaTranslator as _, }; GraphQLParserTranslator::translate_schema(&self.schema) } } impl<'a, S> SchemaType<'a, S> { /// Create a new schema. pub fn new( query_info: &QueryT::TypeInfo, mutation_info: &MutationT::TypeInfo, subscription_info: &SubscriptionT::TypeInfo, ) -> Self where S: ScalarValue + 'a, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { let mut directives = FnvHashMap::default(); let mut registry = Registry::new(FnvHashMap::default()); let query_type_name = registry .get_type::(query_info) .innermost_name() .to_owned(); let mutation_type_name = registry .get_type::(mutation_info) .innermost_name() .to_owned(); let subscription_type_name = registry .get_type::(subscription_info) .innermost_name() .to_owned(); registry.get_type::>(&()); directives.insert("skip".into(), DirectiveType::new_skip(&mut registry)); directives.insert("include".into(), DirectiveType::new_include(&mut registry)); directives.insert( "deprecated".into(), DirectiveType::new_deprecated(&mut registry), ); directives.insert( "specifiedBy".into(), DirectiveType::new_specified_by(&mut registry), ); let mut meta_fields = vec![ registry.field::>("__schema", &()), registry .field::>("__type", &()) .argument(registry.arg::("name", &())), ]; if let Some(root_type) = registry.types.get_mut(&query_type_name) { if let MetaType::Object(ObjectMeta { ref mut fields, .. }) = *root_type { fields.append(&mut meta_fields); } else { panic!("Root type is not an object"); } } else { panic!("Root type not found"); } for meta_type in registry.types.values() { if let MetaType::Placeholder(PlaceholderMeta { ref of_type }) = *meta_type { panic!("Type {of_type:?} is still a placeholder type"); } } SchemaType { description: None, types: registry.types, query_type_name, mutation_type_name: if &mutation_type_name != "_EmptyMutation" { Some(mutation_type_name) } else { None }, subscription_type_name: if &subscription_type_name != "_EmptySubscription" { Some(subscription_type_name) } else { None }, directives, } } /// Add a description. pub fn set_description(&mut self, description: impl Into>) { self.description = Some(description.into()); } /// Add a directive like `skip` or `include`. pub fn add_directive(&mut self, directive: DirectiveType<'a, S>) { self.directives.insert(directive.name.clone(), directive); } /// Get a type by name. pub fn type_by_name(&self, name: &str) -> Option> { self.types.get(name).map(|t| TypeType::Concrete(t)) } /// Get a concrete type by name. pub fn concrete_type_by_name(&self, name: &str) -> Option<&MetaType> { self.types.get(name) } pub(crate) fn lookup_type(&self, tpe: &Type) -> Option<&MetaType> { match *tpe { Type::NonNullNamed(ref name) | Type::Named(ref name) => { self.concrete_type_by_name(name) } Type::List(ref inner, _) | Type::NonNullList(ref inner, _) => self.lookup_type(inner), } } /// Get the query type from the schema. pub fn query_type(&self) -> TypeType { TypeType::Concrete( self.types .get(&self.query_type_name) .expect("Query type does not exist in schema"), ) } /// Get the concrete query type from the schema. pub fn concrete_query_type(&self) -> &MetaType { self.types .get(&self.query_type_name) .expect("Query type does not exist in schema") } /// Get the mutation type from the schema. pub fn mutation_type(&self) -> Option> { self.mutation_type_name.as_ref().map(|name| { self.type_by_name(name) .expect("Mutation type does not exist in schema") }) } /// Get the concrete mutation type from the schema. pub fn concrete_mutation_type(&self) -> Option<&MetaType> { self.mutation_type_name.as_ref().map(|name| { self.concrete_type_by_name(name) .expect("Mutation type does not exist in schema") }) } /// Get the subscription type. pub fn subscription_type(&self) -> Option> { self.subscription_type_name.as_ref().map(|name| { self.type_by_name(name) .expect("Subscription type does not exist in schema") }) } /// Get the concrete subscription type. pub fn concrete_subscription_type(&self) -> Option<&MetaType> { self.subscription_type_name.as_ref().map(|name| { self.concrete_type_by_name(name) .expect("Subscription type does not exist in schema") }) } /// Get a list of types. pub fn type_list(&self) -> Vec> { let mut types = self .types .values() .map(|t| TypeType::Concrete(t)) .collect::>(); sort_concrete_types(&mut types); types } /// Get a list of concrete types. pub fn concrete_type_list(&self) -> Vec<&MetaType> { self.types.values().collect() } /// Make a type. pub fn make_type(&self, t: &Type) -> TypeType { match *t { Type::NonNullNamed(ref n) => TypeType::NonNull(Box::new( self.type_by_name(n).expect("Type not found in schema"), )), Type::NonNullList(ref inner, expected_size) => TypeType::NonNull(Box::new( TypeType::List(Box::new(self.make_type(inner)), expected_size), )), Type::Named(ref n) => self.type_by_name(n).expect("Type not found in schema"), Type::List(ref inner, expected_size) => { TypeType::List(Box::new(self.make_type(inner)), expected_size) } } } /// Get a list of directives. pub fn directive_list(&self) -> Vec<&DirectiveType> { let mut directives = self.directives.values().collect::>(); sort_directives(&mut directives); directives } /// Get directive by name. pub fn directive_by_name(&self, name: &str) -> Option<&DirectiveType> { self.directives.get(name) } /// Determine if there is an overlap between types. pub fn type_overlap(&self, t1: &MetaType, t2: &MetaType) -> bool { if std::ptr::eq(t1, t2) { return true; } match (t1.is_abstract(), t2.is_abstract()) { (true, true) => self .possible_types(t1) .iter() .any(|t| self.is_possible_type(t2, t)), (true, false) => self.is_possible_type(t1, t2), (false, true) => self.is_possible_type(t2, t1), (false, false) => false, } } /// A list of possible typeees for a given type. pub fn possible_types(&self, t: &MetaType) -> Vec<&MetaType> { match *t { MetaType::Union(UnionMeta { ref of_type_names, .. }) => of_type_names .iter() .flat_map(|t| self.concrete_type_by_name(t)) .collect(), MetaType::Interface(InterfaceMeta { ref name, .. }) => self .concrete_type_list() .into_iter() .filter(|t| match **t { MetaType::Object(ObjectMeta { ref interface_names, .. }) => interface_names.iter().any(|iname| iname == name), _ => false, }) .collect(), _ => panic!("Can't retrieve possible types from non-abstract meta type"), } } /// If the abstract type is possible. pub fn is_possible_type( &self, abstract_type: &MetaType, possible_type: &MetaType, ) -> bool { self.possible_types(abstract_type) .into_iter() .any(|t| (std::ptr::eq(t, possible_type))) } /// If the type is a subtype of another type. pub fn is_subtype<'b>(&self, sub_type: &Type<'b>, super_type: &Type<'b>) -> bool { use crate::ast::Type::*; if super_type == sub_type { return true; } match (super_type, sub_type) { (&NonNullNamed(ref super_name), &NonNullNamed(ref sub_name)) | (&Named(ref super_name), &Named(ref sub_name)) | (&Named(ref super_name), &NonNullNamed(ref sub_name)) => { self.is_named_subtype(sub_name, super_name) } (&NonNullList(ref super_inner, _), &NonNullList(ref sub_inner, _)) | (&List(ref super_inner, _), &List(ref sub_inner, _)) | (&List(ref super_inner, _), &NonNullList(ref sub_inner, _)) => { self.is_subtype(sub_inner, super_inner) } _ => false, } } /// If the type is a named subtype. pub fn is_named_subtype(&self, sub_type_name: &str, super_type_name: &str) -> bool { if sub_type_name == super_type_name { true } else if let (Some(sub_type), Some(super_type)) = ( self.concrete_type_by_name(sub_type_name), self.concrete_type_by_name(super_type_name), ) { super_type.is_abstract() && self.is_possible_type(super_type, sub_type) } else { false } } } impl<'a, S> TypeType<'a, S> { #[inline] pub fn to_concrete(&self) -> Option<&'a MetaType> { match *self { TypeType::Concrete(t) => Some(t), _ => None, } } #[inline] pub fn innermost_concrete(&self) -> &'a MetaType { match *self { TypeType::Concrete(t) => t, TypeType::NonNull(ref n) | TypeType::List(ref n, _) => n.innermost_concrete(), } } #[inline] pub fn list_contents(&self) -> Option<&TypeType<'a, S>> { match *self { TypeType::List(ref n, _) => Some(n), TypeType::NonNull(ref n) => n.list_contents(), _ => None, } } #[inline] pub fn is_non_null(&self) -> bool { matches!(*self, TypeType::NonNull(_)) } } impl<'a, S> DirectiveType<'a, S> where S: ScalarValue + 'a, { pub fn new( name: &str, locations: &[DirectiveLocation], arguments: &[Argument<'a, S>], is_repeatable: bool, ) -> Self { Self { name: name.into(), description: None, locations: locations.to_vec(), arguments: arguments.to_vec(), is_repeatable, } } fn new_skip(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S> where S: ScalarValue, { Self::new( "skip", &[ DirectiveLocation::Field, DirectiveLocation::FragmentSpread, DirectiveLocation::InlineFragment, ], &[registry.arg::("if", &())], false, ) } fn new_include(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S> where S: ScalarValue, { Self::new( "include", &[ DirectiveLocation::Field, DirectiveLocation::FragmentSpread, DirectiveLocation::InlineFragment, ], &[registry.arg::("if", &())], false, ) } fn new_deprecated(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S> where S: ScalarValue, { Self::new( "deprecated", &[ DirectiveLocation::FieldDefinition, DirectiveLocation::EnumValue, ], &[registry.arg::("reason", &())], false, ) } fn new_specified_by(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S> where S: ScalarValue, { Self::new( "specifiedBy", &[DirectiveLocation::Scalar], &[registry.arg::("url", &())], false, ) } pub fn description(mut self, description: &str) -> DirectiveType<'a, S> { self.description = Some(description.into()); self } } impl fmt::Display for DirectiveLocation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { Self::Query => "query", Self::Mutation => "mutation", Self::Subscription => "subscription", Self::Field => "field", Self::FieldDefinition => "field definition", Self::FragmentDefinition => "fragment definition", Self::FragmentSpread => "fragment spread", Self::InlineFragment => "inline fragment", Self::VariableDefinition => "variable definition", Self::Scalar => "scalar", Self::EnumValue => "enum value", }) } } impl<'a, S> fmt::Display for TypeType<'a, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Concrete(t) => f.write_str(t.name().unwrap()), Self::List(i, _) => write!(f, "[{i}]"), Self::NonNull(i) => write!(f, "{i}!"), } } } /// Sorts the provided [`TypeType`]s in the "type-then-name" manner. fn sort_concrete_types(types: &mut [TypeType]) { types.sort_by(|a, b| { concrete_type_sort::by_type(a) .cmp(&concrete_type_sort::by_type(b)) .then_with(|| concrete_type_sort::by_name(a).cmp(&concrete_type_sort::by_name(b))) }); } /// Sorts the provided [`DirectiveType`]s by name. fn sort_directives(directives: &mut [&DirectiveType]) { directives.sort_by(|a, b| a.name.cmp(&b.name)); } /// Evaluation of a [`TypeType`] weights for sorting (for concrete types only). /// /// Used for deterministic introspection output. mod concrete_type_sort { use crate::meta::MetaType; use super::TypeType; /// Returns a [`TypeType`] sorting weight by its type. pub fn by_type(t: &TypeType) -> u8 { match t { TypeType::Concrete(MetaType::Enum(_)) => 0, TypeType::Concrete(MetaType::InputObject(_)) => 1, TypeType::Concrete(MetaType::Interface(_)) => 2, TypeType::Concrete(MetaType::Scalar(_)) => 3, TypeType::Concrete(MetaType::Object(_)) => 4, TypeType::Concrete(MetaType::Union(_)) => 5, // NOTE: The following types are not part of the introspected types. TypeType::Concrete( MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_), ) => 6, // NOTE: Other variants will not appear since we're only sorting concrete types. TypeType::List(..) | TypeType::NonNull(_) => 7, } } /// Returns a [`TypeType`] sorting weight by its name. pub fn by_name<'a, S>(t: &'a TypeType<'a, S>) -> Option<&'a str> { match t { TypeType::Concrete(MetaType::Enum(meta)) => Some(&meta.name), TypeType::Concrete(MetaType::InputObject(meta)) => Some(&meta.name), TypeType::Concrete(MetaType::Interface(meta)) => Some(&meta.name), TypeType::Concrete(MetaType::Scalar(meta)) => Some(&meta.name), TypeType::Concrete(MetaType::Object(meta)) => Some(&meta.name), TypeType::Concrete(MetaType::Union(meta)) => Some(&meta.name), TypeType::Concrete( // NOTE: The following types are not part of the introspected types. MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_), ) // NOTE: Other variants will not appear since we're only sorting concrete types. | TypeType::List(..) | TypeType::NonNull(_) => None, } } } #[cfg(test)] mod root_node_test { #[cfg(feature = "schema-language")] mod as_document { use crate::{graphql_object, EmptyMutation, EmptySubscription, RootNode}; struct Query; #[graphql_object] impl Query { fn blah() -> bool { true } } #[test] fn generates_correct_document() { let schema = RootNode::new( Query, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let ast = graphql_parser::parse_schema::<&str>( //language=GraphQL r#" type Query { blah: Boolean! } schema { query: Query } "#, ) .unwrap(); assert_eq!(ast.to_string(), schema.as_document().to_string()); } } #[cfg(feature = "schema-language")] mod as_sdl { use crate::{ graphql_object, EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLUnion, RootNode, }; #[derive(GraphQLObject, Default)] struct Cake { fresh: bool, } #[derive(GraphQLObject, Default)] struct IceCream { cold: bool, } #[derive(GraphQLUnion)] enum GlutenFree { Cake(Cake), IceCream(IceCream), } #[derive(GraphQLEnum)] enum Fruit { Apple, Orange, } #[derive(GraphQLInputObject)] struct Coordinate { latitude: f64, longitude: f64, } struct Query; #[graphql_object] impl Query { fn blah() -> bool { true } /// This is whatever's description. fn whatever() -> String { "foo".into() } fn arr(stuff: Vec) -> Option<&'static str> { (!stuff.is_empty()).then_some("stuff") } fn fruit() -> Fruit { Fruit::Apple } fn gluten_free(flavor: String) -> GlutenFree { if flavor == "savory" { GlutenFree::Cake(Cake::default()) } else { GlutenFree::IceCream(IceCream::default()) } } #[deprecated] fn old() -> i32 { 42 } #[deprecated(note = "This field is deprecated, use another.")] fn really_old() -> f64 { 42.0 } } #[test] fn generates_correct_sdl() { let actual = RootNode::new( Query, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); let expected = graphql_parser::parse_schema::<&str>( //language=GraphQL r#" schema { query: Query } enum Fruit { APPLE ORANGE } input Coordinate { latitude: Float! longitude: Float! } type Cake { fresh: Boolean! } type IceCream { cold: Boolean! } type Query { blah: Boolean! "This is whatever's description." whatever: String! arr(stuff: [Coordinate!]!): String fruit: Fruit! glutenFree(flavor: String!): GlutenFree! old: Int! @deprecated reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.") } union GlutenFree = Cake | IceCream "#, ) .unwrap(); assert_eq!(actual.as_sdl(), expected.to_string()); } } } juniper-0.16.2/src/schema/schema.rs000064400000000000000000000326521046102023000152330ustar 00000000000000use crate::{ ast::Selection, executor::{ExecutionResult, Executor, Registry}, graphql_object, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, }, value::{ScalarValue, Value}, }; use crate::schema::{ meta::{ Argument, EnumMeta, EnumValue, Field, InputObjectMeta, InterfaceMeta, MetaType, ObjectMeta, UnionMeta, }, model::{DirectiveLocation, DirectiveType, RootNode, SchemaType, TypeType}, }; impl<'a, S, QueryT, MutationT, SubscriptionT> GraphQLType for RootNode<'a, QueryT, MutationT, SubscriptionT, S> where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { fn name(info: &Self::TypeInfo) -> Option<&str> { QueryT::name(info) } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { QueryT::meta(info, registry) } } impl<'a, S, QueryT, MutationT, SubscriptionT> GraphQLValue for RootNode<'a, QueryT, MutationT, SubscriptionT, S> where S: ScalarValue, QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, { type Context = QueryT::Context; type TypeInfo = QueryT::TypeInfo; fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { QueryT::name(info) } fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { self.query_type.concrete_type_name(context, info) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, args: &Arguments, executor: &Executor, ) -> ExecutionResult { match field { "__schema" => executor .replaced_context(&self.schema) .resolve(&(), &self.schema), "__type" => { let type_name: String = args.get("name")?.unwrap(); executor .replaced_context(&self.schema) .resolve(&(), &self.schema.type_by_name(&type_name)) } _ => self.query_type.resolve_field(info, field, args, executor), } } fn resolve( &self, info: &Self::TypeInfo, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { use crate::{types::base::resolve_selection_set_into, value::Object}; if let Some(selection_set) = selection_set { let mut result = Object::with_capacity(selection_set.len()); if resolve_selection_set_into(self, info, selection_set, executor, &mut result) { Ok(Value::Object(result)) } else { Ok(Value::null()) } } else { // TODO: this panic seems useless, investigate why it is here. panic!("resolve() must be implemented by non-object output types"); } } } impl<'a, S, QueryT, MutationT, SubscriptionT> GraphQLValueAsync for RootNode<'a, QueryT, MutationT, SubscriptionT, S> where QueryT: GraphQLTypeAsync, QueryT::TypeInfo: Sync, QueryT::Context: Sync + 'a, MutationT: GraphQLTypeAsync, MutationT::TypeInfo: Sync, SubscriptionT: GraphQLType + Sync, SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, { fn resolve_field_async<'b>( &'b self, info: &'b Self::TypeInfo, field_name: &'b str, arguments: &'b Arguments, executor: &'b Executor, ) -> crate::BoxFuture<'b, ExecutionResult> { use futures::future::ready; match field_name { "__schema" | "__type" => { let v = self.resolve_field(info, field_name, arguments, executor); Box::pin(ready(v)) } _ => self .query_type .resolve_field_async(info, field_name, arguments, executor), } } } #[graphql_object( name = "__Schema" context = SchemaType<'a, S>, scalar = S, internal, )] impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> { fn description(&self) -> Option<&str> { self.description.as_deref() } fn types(&self) -> Vec> { self.type_list() .into_iter() .filter(|t| { t.to_concrete() .map(|t| { !(t.name() == Some("_EmptyMutation") || t.name() == Some("_EmptySubscription")) }) .unwrap_or(false) }) .collect() } #[graphql(name = "queryType")] fn query_type_(&self) -> TypeType { self.query_type() } #[graphql(name = "mutationType")] fn mutation_type_(&self) -> Option> { self.mutation_type() } #[graphql(name = "subscriptionType")] fn subscription_type_(&self) -> Option> { self.subscription_type() } fn directives(&self) -> Vec<&DirectiveType> { self.directive_list() } } #[graphql_object( name = "__Type" context = SchemaType<'a, S>, scalar = S, internal, )] impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn name(&self) -> Option<&str> { match self { TypeType::Concrete(t) => t.name(), _ => None, } } fn description(&self) -> Option<&str> { match self { TypeType::Concrete(t) => t.description(), _ => None, } } fn specified_by_url(&self) -> Option<&str> { match self { Self::Concrete(t) => t.specified_by_url(), Self::NonNull(_) | Self::List(..) => None, } } fn kind(&self) -> TypeKind { match self { TypeType::Concrete(t) => t.type_kind(), TypeType::List(..) => TypeKind::List, TypeType::NonNull(_) => TypeKind::NonNull, } } fn fields( &self, #[graphql(default = false)] include_deprecated: Option, ) -> Option>> { match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( fields .iter() .filter(|f| { include_deprecated.unwrap_or_default() || !f.deprecation_status.is_deprecated() }) .filter(|f| !f.name.starts_with("__")) .collect(), ), _ => None, } } fn of_type(&self) -> Option<&TypeType> { match self { TypeType::Concrete(_) => None, TypeType::List(l, _) | TypeType::NonNull(l) => Some(&**l), } } fn input_fields(&self) -> Option<&[Argument]> { match self { TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, .. })) => Some(input_fields.as_slice()), _ => None, } } fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { match self { TypeType::Concrete( &MetaType::Object(ObjectMeta { ref interface_names, .. }) | &MetaType::Interface(InterfaceMeta { ref interface_names, .. }), ) => Some( interface_names .iter() .filter_map(|n| context.type_by_name(n)) .collect(), ), _ => None, } } fn possible_types<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { match self { TypeType::Concrete(&MetaType::Union(UnionMeta { ref of_type_names, .. })) => Some( of_type_names .iter() .filter_map(|tn| context.type_by_name(tn)) .collect(), ), TypeType::Concrete(&MetaType::Interface(InterfaceMeta { name: ref iface_name, .. })) => { let mut type_names = context .types .values() .filter_map(|ct| { if let MetaType::Object(ObjectMeta { name, interface_names, .. }) = ct { interface_names .iter() .any(|iname| iname == iface_name) .then(|| name.as_ref()) } else { None } }) .collect::>(); type_names.sort(); Some( type_names .into_iter() .filter_map(|n| context.type_by_name(n)) .collect(), ) } _ => None, } } fn enum_values( &self, #[graphql(default = false)] include_deprecated: Option, ) -> Option> { match self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values .iter() .filter(|f| { include_deprecated.unwrap_or_default() || !f.deprecation_status.is_deprecated() }) .collect(), ), _ => None, } } } #[graphql_object( name = "__Field", context = SchemaType<'a, S>, scalar = S, internal, )] impl<'a, S: ScalarValue + 'a> Field<'a, S> { fn name(&self) -> String { self.name.clone().into() } #[graphql(name = "description")] fn description_(&self) -> Option<&str> { self.description.as_deref() } fn args(&self) -> Vec<&Argument> { self.arguments .as_ref() .map_or_else(Vec::new, |v| v.iter().collect()) } #[graphql(name = "type")] fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> { context.make_type(&self.field_type) } fn is_deprecated(&self) -> bool { self.deprecation_status.is_deprecated() } fn deprecation_reason(&self) -> Option<&str> { self.deprecation_status.reason() } } #[graphql_object( name = "__InputValue", context = SchemaType<'a, S>, scalar = S, internal, )] impl<'a, S: ScalarValue + 'a> Argument<'a, S> { fn name(&self) -> &str { &self.name } #[graphql(name = "description")] fn description_(&self) -> Option<&str> { self.description.as_deref() } #[graphql(name = "type")] fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> { context.make_type(&self.arg_type) } #[graphql(name = "defaultValue")] fn default_value_(&self) -> Option { self.default_value.as_ref().map(ToString::to_string) } } #[graphql_object(name = "__EnumValue", internal)] impl EnumValue { fn name(&self) -> &str { &self.name } #[graphql(name = "description")] fn description_(&self) -> Option<&str> { self.description.as_deref() } fn is_deprecated(&self) -> bool { self.deprecation_status.is_deprecated() } fn deprecation_reason(&self) -> Option<&str> { self.deprecation_status.reason() } } #[graphql_object( name = "__Directive", context = SchemaType<'a, S>, scalar = S, internal, )] impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> { fn name(&self) -> &str { &self.name } #[graphql(name = "description")] fn description_(&self) -> Option<&str> { self.description.as_deref() } fn locations(&self) -> &[DirectiveLocation] { &self.locations } fn is_repeatable(&self) -> bool { self.is_repeatable } fn args(&self) -> &[Argument] { &self.arguments } // Included for compatibility with the introspection query in GraphQL.js #[graphql(deprecated = "Use the locations array instead")] fn on_operation(&self) -> bool { self.locations.contains(&DirectiveLocation::Query) } // Included for compatibility with the introspection query in GraphQL.js #[graphql(deprecated = "Use the locations array instead")] fn on_fragment(&self) -> bool { self.locations .contains(&DirectiveLocation::FragmentDefinition) || self.locations.contains(&DirectiveLocation::InlineFragment) || self.locations.contains(&DirectiveLocation::FragmentSpread) } // Included for compatibility with the introspection query in GraphQL.js #[graphql(deprecated = "Use the locations array instead")] fn on_field(&self) -> bool { self.locations.contains(&DirectiveLocation::Field) } } juniper-0.16.2/src/schema/translate/graphql_parser.rs000064400000000000000000000375051046102023000210040ustar 00000000000000use std::{boxed::Box, collections::BTreeMap}; use graphql_parser::{ query::{Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType}, schema::{ Definition, Document, EnumType as ExternalEnum, EnumValue as ExternalEnumValue, Field as ExternalField, InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue, InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType, ScalarType as ExternalScalarType, SchemaDefinition, Text, TypeDefinition as ExternalTypeDefinition, UnionType as ExternalUnionType, Value as ExternalValue, }, Pos, }; use crate::{ ast::{InputValue, Type}, schema::{ meta::{Argument, DeprecationStatus, EnumValue, Field, MetaType}, model::SchemaType, translate::SchemaTranslator, }, value::ScalarValue, }; pub struct GraphQLParserTranslator; impl<'a, S: 'a, T> From<&'a SchemaType<'a, S>> for Document<'a, T> where S: ScalarValue, T: Text<'a> + Default, { fn from(input: &'a SchemaType<'a, S>) -> Document<'a, T> { GraphQLParserTranslator::translate_schema(input) } } impl<'a, T> SchemaTranslator<'a, graphql_parser::schema::Document<'a, T>> for GraphQLParserTranslator where T: Text<'a> + Default, { fn translate_schema(input: &'a SchemaType) -> graphql_parser::schema::Document<'a, T> where S: ScalarValue, { let mut doc = Document::default(); // Translate type defs. let mut types = input .types .iter() .filter(|(_, meta)| !meta.is_builtin()) .map(|(_, meta)| GraphQLParserTranslator::translate_meta(meta)) .map(Definition::TypeDefinition) .collect(); doc.definitions.append(&mut types); doc.definitions .push(Definition::SchemaDefinition(SchemaDefinition { position: Pos::default(), directives: vec![], query: Some(From::from(input.query_type_name.as_str())), mutation: input .mutation_type_name .as_ref() .map(|s| From::from(s.as_str())), subscription: input .subscription_type_name .as_ref() .map(|s| From::from(s.as_str())), })); doc } } impl GraphQLParserTranslator { fn translate_argument<'a, S, T>(input: &'a Argument) -> ExternalInputValue<'a, T> where S: ScalarValue, T: Text<'a>, { ExternalInputValue { position: Pos::default(), description: input.description.as_ref().map(From::from), name: From::from(input.name.as_str()), value_type: GraphQLParserTranslator::translate_type(&input.arg_type), default_value: input .default_value .as_ref() .map(|x| GraphQLParserTranslator::translate_value(x)), directives: vec![], } } fn translate_value<'a, S: 'a, T>(input: &'a InputValue) -> ExternalValue<'a, T> where S: ScalarValue, T: Text<'a>, { match input { InputValue::Null => ExternalValue::Null, InputValue::Scalar(x) => { if let Some(v) = x.as_string() { ExternalValue::String(v) } else if let Some(v) = x.as_int() { ExternalValue::Int(ExternalNumber::from(v)) } else if let Some(v) = x.as_float() { ExternalValue::Float(v) } else if let Some(v) = x.as_bool() { ExternalValue::Boolean(v) } else { panic!("unknown argument type") } } InputValue::Enum(x) => ExternalValue::Enum(From::from(x.as_str())), InputValue::Variable(x) => ExternalValue::Variable(From::from(x.as_str())), InputValue::List(x) => ExternalValue::List( x.iter() .map(|s| GraphQLParserTranslator::translate_value(&s.item)) .collect(), ), InputValue::Object(x) => { let mut fields = BTreeMap::new(); x.iter().for_each(|(name_span, value_span)| { fields.insert( From::from(name_span.item.as_str()), GraphQLParserTranslator::translate_value(&value_span.item), ); }); ExternalValue::Object(fields) } } } fn translate_type<'a, T>(input: &'a Type<'a>) -> ExternalType<'a, T> where T: Text<'a>, { match input { Type::Named(x) => ExternalType::NamedType(From::from(x.as_ref())), Type::List(x, _) => { ExternalType::ListType(GraphQLParserTranslator::translate_type(x).into()) } Type::NonNullNamed(x) => { ExternalType::NonNullType(Box::new(ExternalType::NamedType(From::from(x.as_ref())))) } Type::NonNullList(x, _) => ExternalType::NonNullType(Box::new(ExternalType::ListType( Box::new(GraphQLParserTranslator::translate_type(x)), ))), } } fn translate_meta<'a, S, T>(input: &'a MetaType) -> ExternalTypeDefinition<'a, T> where S: ScalarValue, T: Text<'a>, { match input { MetaType::Scalar(x) => ExternalTypeDefinition::Scalar(ExternalScalarType { position: Pos::default(), description: x.description.as_ref().map(From::from), name: From::from(x.name.as_ref()), directives: vec![], }), MetaType::Enum(x) => ExternalTypeDefinition::Enum(ExternalEnum { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), directives: vec![], values: x .values .iter() .map(GraphQLParserTranslator::translate_enum_value) .collect(), }), MetaType::Union(x) => ExternalTypeDefinition::Union(ExternalUnionType { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), directives: vec![], types: x .of_type_names .iter() .map(|s| From::from(s.as_str())) .collect(), }), MetaType::Interface(x) => ExternalTypeDefinition::Interface(ExternalInterfaceType { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), implements_interfaces: x .interface_names .iter() .map(|s| From::from(s.as_str())) .collect(), directives: vec![], fields: x .fields .iter() .filter(|x| !x.is_builtin()) .map(GraphQLParserTranslator::translate_field) .collect(), }), MetaType::InputObject(x) => { ExternalTypeDefinition::InputObject(ExternalInputObjectType { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), directives: vec![], fields: x .input_fields .iter() .filter(|x| !x.is_builtin()) .map(GraphQLParserTranslator::translate_argument) .collect(), }) } MetaType::Object(x) => ExternalTypeDefinition::Object(ExternalObjectType { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), directives: vec![], fields: x .fields .iter() .filter(|x| !x.is_builtin()) .map(GraphQLParserTranslator::translate_field) .collect(), implements_interfaces: x .interface_names .iter() .map(|s| From::from(s.as_str())) .collect(), }), _ => panic!("unknown meta type when translating"), } } fn translate_enum_value<'a, T>(input: &'a EnumValue) -> ExternalEnumValue<'a, T> where T: Text<'a>, { ExternalEnumValue { position: Pos::default(), name: From::from(input.name.as_ref()), description: input.description.as_ref().map(|s| From::from(s.as_str())), directives: generate_directives(&input.deprecation_status), } } fn translate_field<'a, S: 'a, T>(input: &'a Field) -> ExternalField<'a, T> where S: ScalarValue, T: Text<'a>, { let arguments = input .arguments .as_ref() .map(|a| { a.iter() .filter(|x| !x.is_builtin()) .map(|x| GraphQLParserTranslator::translate_argument(x)) .collect() }) .unwrap_or_default(); ExternalField { position: Pos::default(), name: From::from(input.name.as_str()), description: input.description.as_ref().map(|s| From::from(s.as_str())), directives: generate_directives(&input.deprecation_status), field_type: GraphQLParserTranslator::translate_type(&input.field_type), arguments, } } } fn deprecation_to_directive<'a, T>(status: &DeprecationStatus) -> Option> where T: Text<'a>, { match status { DeprecationStatus::Current => None, DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { position: Pos::default(), name: "deprecated".into(), arguments: reason .as_ref() .map(|rsn| vec![(From::from("reason"), ExternalValue::String(rsn.into()))]) .unwrap_or_default(), }), } } // Right now the only directive supported is `@deprecated`. // `@skip` and `@include` are dealt with elsewhere. // https://spec.graphql.org/October2021#sec-Type-System.Directives.Built-in-Directives fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec> where T: Text<'a>, { if let Some(d) = deprecation_to_directive(status) { vec![d] } else { vec![] } } /// Sorts the provided [`Document`] in the "type-then-name" manner. pub(crate) fn sort_schema_document<'a, T: Text<'a>>(document: &mut Document<'a, T>) { document.definitions.sort_by(move |a, b| { let type_cmp = sort_value::by_type(a).cmp(&sort_value::by_type(b)); let name_cmp = sort_value::by_is_directive(a) .cmp(&sort_value::by_is_directive(b)) .then(sort_value::by_name(a).cmp(&sort_value::by_name(b))) .then(sort_value::by_directive(a).cmp(&sort_value::by_directive(b))); type_cmp.then(name_cmp) }) } /// Evaluation of a [`Definition`] weights for sorting. mod sort_value { use graphql_parser::schema::{Definition, Text, TypeDefinition, TypeExtension}; /// Returns a [`Definition`] sorting weight by its type. pub(super) fn by_type<'a, T>(definition: &Definition<'a, T>) -> u8 where T: Text<'a>, { match definition { Definition::SchemaDefinition(_) => 0, Definition::DirectiveDefinition(_) => 1, Definition::TypeDefinition(t) => match t { TypeDefinition::Enum(_) => 2, TypeDefinition::InputObject(_) => 4, TypeDefinition::Interface(_) => 6, TypeDefinition::Scalar(_) => 8, TypeDefinition::Object(_) => 10, TypeDefinition::Union(_) => 12, }, Definition::TypeExtension(e) => match e { TypeExtension::Enum(_) => 3, TypeExtension::InputObject(_) => 5, TypeExtension::Interface(_) => 7, TypeExtension::Scalar(_) => 9, TypeExtension::Object(_) => 11, TypeExtension::Union(_) => 13, }, } } /// Returns a [`Definition`] sorting weight by its name. pub(super) fn by_name<'b, 'a, T>(definition: &'b Definition<'a, T>) -> Option<&'b T::Value> where T: Text<'a>, { match definition { Definition::SchemaDefinition(_) => None, Definition::DirectiveDefinition(d) => Some(&d.name), Definition::TypeDefinition(t) => match t { TypeDefinition::Enum(d) => Some(&d.name), TypeDefinition::InputObject(d) => Some(&d.name), TypeDefinition::Interface(d) => Some(&d.name), TypeDefinition::Scalar(d) => Some(&d.name), TypeDefinition::Object(d) => Some(&d.name), TypeDefinition::Union(d) => Some(&d.name), }, Definition::TypeExtension(e) => match e { TypeExtension::Enum(d) => Some(&d.name), TypeExtension::InputObject(d) => Some(&d.name), TypeExtension::Interface(d) => Some(&d.name), TypeExtension::Scalar(d) => Some(&d.name), TypeExtension::Object(d) => Some(&d.name), TypeExtension::Union(d) => Some(&d.name), }, } } /// Returns a [`Definition`] sorting weight by its directive. pub(super) fn by_directive<'b, 'a, T>(definition: &'b Definition<'a, T>) -> Option<&'b T::Value> where T: Text<'a>, { match definition { Definition::SchemaDefinition(_) => None, Definition::DirectiveDefinition(_) => None, Definition::TypeDefinition(t) => match t { TypeDefinition::Enum(d) => d.directives.first().map(|d| &d.name), TypeDefinition::InputObject(d) => d.directives.first().map(|d| &d.name), TypeDefinition::Interface(d) => d.directives.first().map(|d| &d.name), TypeDefinition::Scalar(d) => d.directives.first().map(|d| &d.name), TypeDefinition::Object(d) => d.directives.first().map(|d| &d.name), TypeDefinition::Union(d) => d.directives.first().map(|d| &d.name), }, Definition::TypeExtension(e) => match e { TypeExtension::Enum(d) => d.directives.first().map(|d| &d.name), TypeExtension::InputObject(d) => d.directives.first().map(|d| &d.name), TypeExtension::Interface(d) => d.directives.first().map(|d| &d.name), TypeExtension::Scalar(d) => d.directives.first().map(|d| &d.name), TypeExtension::Object(d) => d.directives.first().map(|d| &d.name), TypeExtension::Union(d) => d.directives.first().map(|d| &d.name), }, } } /// Returns a [`Definition`] sorting weight by whether it represents a directive. pub(super) fn by_is_directive<'a, T>(definition: &Definition<'a, T>) -> u8 where T: Text<'a>, { match definition { Definition::SchemaDefinition(_) => 0, Definition::DirectiveDefinition(_) => 1, _ => 2, } } } juniper-0.16.2/src/schema/translate/mod.rs000064400000000000000000000003231046102023000165350ustar 00000000000000use crate::{ScalarValue, SchemaType}; pub trait SchemaTranslator<'a, T> { fn translate_schema(s: &'a SchemaType) -> T; } #[cfg(feature = "schema-language")] pub mod graphql_parser; juniper-0.16.2/src/tests/fixtures/mod.rs000064400000000000000000000001241046102023000163120ustar 00000000000000//! Library fixtures /// GraphQL schema and data from Star Wars. pub mod starwars; juniper-0.16.2/src/tests/fixtures/starwars/mod.rs000064400000000000000000000001151046102023000201600ustar 00000000000000pub mod schema; #[cfg(feature = "schema-language")] pub mod schema_language; juniper-0.16.2/src/tests/fixtures/starwars/schema.rs000064400000000000000000000206051046102023000206470ustar 00000000000000#![allow(missing_docs)] use std::{collections::HashMap, pin::Pin}; use crate::{graphql_interface, graphql_object, graphql_subscription, Context, GraphQLEnum}; #[derive(Clone, Copy, Debug)] pub struct Query; #[graphql_object(context = Database)] /// The root query object of the schema impl Query { fn human( #[graphql(context)] database: &Database, #[graphql(description = "id of the human")] id: String, ) -> Option<&Human> { database.get_human(&id) } fn droid( #[graphql(context)] database: &Database, #[graphql(description = "id of the droid")] id: String, ) -> Option<&Droid> { database.get_droid(&id) } fn hero( #[graphql(context)] database: &Database, #[graphql(description = "If omitted, returns the hero of the whole saga. \ If provided, returns the hero of that particular episode")] episode: Option, ) -> Option { Some(database.get_hero(episode)) } } #[derive(Clone, Copy, Debug)] pub struct Subscription; type HumanStream = Pin + Send>>; #[graphql_subscription(context = Database)] /// Super basic subscription fixture impl Subscription { async fn async_human(context: &Database) -> HumanStream { let human = context.get_human("1000").unwrap().clone(); Box::pin(futures::stream::once(futures::future::ready(human))) } } #[derive(GraphQLEnum, Clone, Copy, Debug, Eq, PartialEq)] pub enum Episode { #[graphql(name = "NEW_HOPE")] NewHope, Empire, Jedi, } #[graphql_interface(for = [Human, Droid], context = Database)] /// A character in the Star Wars Trilogy pub trait Character { /// The id of the character fn id(&self) -> &str; /// The name of the character fn name(&self) -> Option<&str>; /// The friends of the character fn friends(&self, ctx: &Database) -> Vec; /// Which movies they appear in fn appears_in(&self) -> &[Episode]; #[graphql(ignore)] fn friends_ids(&self) -> &[String]; } #[derive(Clone)] pub struct Human { id: String, name: String, friend_ids: Vec, appears_in: Vec, #[allow(dead_code)] secret_backstory: Option, home_planet: Option, } impl Human { pub fn new( id: &str, name: &str, friend_ids: &[&str], appears_in: &[Episode], secret_backstory: Option<&str>, home_planet: Option<&str>, ) -> Self { Self { id: id.into(), name: name.into(), friend_ids: friend_ids.iter().copied().map(Into::into).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(Into::into), home_planet: home_planet.map(Into::into), } } } /// A humanoid creature in the Star Wars universe. #[graphql_object(context = Database, impl = CharacterValue)] impl Human { /// The id of the human pub fn id(&self) -> &str { &self.id } /// The name of the human pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the human pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The home planet of the human pub fn home_planet(&self) -> &Option { &self.home_planet } } #[derive(Clone)] pub struct Droid { id: String, name: String, friend_ids: Vec, appears_in: Vec, #[allow(dead_code)] secret_backstory: Option, primary_function: Option, } impl Droid { pub fn new( id: &str, name: &str, friend_ids: &[&str], appears_in: &[Episode], secret_backstory: Option<&str>, primary_function: Option<&str>, ) -> Self { Self { id: id.into(), name: name.into(), friend_ids: friend_ids.iter().copied().map(Into::into).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(Into::into), primary_function: primary_function.map(Into::into), } } } /// A mechanical creature in the Star Wars universe. #[graphql_object(context = Database, impl = CharacterValue)] impl Droid { /// The id of the droid pub fn id(&self) -> &str { &self.id } /// The name of the droid pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the droid pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The primary function of the droid pub fn primary_function(&self) -> &Option { &self.primary_function } } #[derive(Clone, Default)] pub struct Database { humans: HashMap, droids: HashMap, } impl Context for Database {} impl Database { pub fn new() -> Database { let mut humans = HashMap::new(); let mut droids = HashMap::new(); humans.insert( "1000".into(), Human::new( "1000", "Luke Skywalker", &["1002", "1003", "2000", "2001"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, Some("Tatooine"), ), ); humans.insert( "1001".into(), Human::new( "1001", "Darth Vader", &["1004"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, Some("Tatooine"), ), ); humans.insert( "1002".into(), Human::new( "1002", "Han Solo", &["1000", "1003", "2001"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, None, ), ); humans.insert( "1003".into(), Human::new( "1003", "Leia Organa", &["1000", "1002", "2000", "2001"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, Some("Alderaan"), ), ); humans.insert( "1004".into(), Human::new( "1004", "Wilhuff Tarkin", &["1001"], &[Episode::NewHope], None, None, ), ); droids.insert( "2000".into(), Droid::new( "2000", "C-3PO", &["1000", "1002", "1003", "2001"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, Some("Protocol"), ), ); droids.insert( "2001".into(), Droid::new( "2001", "R2-D2", &["1000", "1002", "1003"], &[Episode::NewHope, Episode::Empire, Episode::Jedi], None, Some("Astromech"), ), ); Database { humans, droids } } pub fn get_hero(&self, episode: Option) -> CharacterValue { if episode == Some(Episode::Empire) { self.get_human("1000").unwrap().clone().into() } else { self.get_droid("2001").unwrap().clone().into() } } pub fn get_human(&self, id: &str) -> Option<&Human> { self.humans.get(id) } pub fn get_droid(&self, id: &str) -> Option<&Droid> { self.droids.get(id) } pub fn get_character(&self, id: &str) -> Option { #[allow(clippy::manual_map)] if let Some(h) = self.humans.get(id) { Some(h.clone().into()) } else if let Some(d) = self.droids.get(id) { Some(d.clone().into()) } else { None } } pub fn get_friends(&self, ids: &[String]) -> Vec { ids.iter().flat_map(|id| self.get_character(id)).collect() } } juniper-0.16.2/src/tests/fixtures/starwars/schema_language.rs000064400000000000000000000023701046102023000225110ustar 00000000000000#![allow(missing_docs)] /// The schema as a static/hardcoded GraphQL SDL (schema definition language). pub const STATIC_GRAPHQL_SCHEMA_DEFINITION: &str = include_str!("starwars.graphql"); #[cfg(test)] mod tests { use pretty_assertions::assert_eq; use crate::{ schema::model::RootNode, tests::fixtures::starwars::{ schema::{Database, Query}, schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION, }, types::scalars::{EmptyMutation, EmptySubscription}, }; #[test] fn dynamic_schema_language_matches_static() { let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); //dbg!("{}", schema.as_sdl()); // `include_str()` keeps line endings. `git` will sadly by default // convert them, making this test fail without runtime tweaks on // Windows. // // See https://github.com/rust-lang/rust/pull/63681. #[cfg(windows)] let expected = STATIC_GRAPHQL_SCHEMA_DEFINITION.replace("\r\n", "\n"); #[cfg(not(windows))] let expected = STATIC_GRAPHQL_SCHEMA_DEFINITION; assert_eq!(schema.as_sdl(), expected); } } juniper-0.16.2/src/tests/fixtures/starwars/starwars.graphql000064400000000000000000000024221046102023000222640ustar 00000000000000schema { query: Query } enum Episode { NEW_HOPE EMPIRE JEDI } "A character in the Star Wars Trilogy" interface Character { "The id of the character" id: String! "The name of the character" name: String "The friends of the character" friends: [Character!]! "Which movies they appear in" appearsIn: [Episode!]! } "A mechanical creature in the Star Wars universe." type Droid implements Character { "The id of the droid" id: String! "The name of the droid" name: String "The friends of the droid" friends: [Character!]! "Which movies they appear in" appearsIn: [Episode!]! "The primary function of the droid" primaryFunction: String } "A humanoid creature in the Star Wars universe." type Human implements Character { "The id of the human" id: String! "The name of the human" name: String "The friends of the human" friends: [Character!]! "Which movies they appear in" appearsIn: [Episode!]! "The home planet of the human" homePlanet: String } "The root query object of the schema" type Query { human("id of the human" id: String!): Human droid("id of the droid" id: String!): Droid hero("If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" episode: Episode): Character } juniper-0.16.2/src/tests/introspection_tests.rs000064400000000000000000000175531046102023000200220ustar 00000000000000use std::collections::HashSet; use pretty_assertions::assert_eq; use crate::{ graphql_vars, introspection::IntrospectionFormat, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, }; use super::schema_introspection::*; #[tokio::test] async fn test_introspection_query_type_name() { let doc = r#" query IntrospectionQueryTypeQuery { __schema { queryType { name } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "__schema": { "queryType": { "name": "Query" } } }), vec![] )) ); } #[tokio::test] async fn test_introspection_type_name() { let doc = r#" query IntrospectionQueryTypeQuery { __type(name: "Droid") { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "__type": { "name": "Droid", }, }), vec![] )) ); } #[tokio::test] async fn test_introspection_specific_object_type_name_and_kind() { let doc = r#" query IntrospectionDroidKindQuery { __type(name: "Droid") { name kind } } "#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "__type": { "name": "Droid", "kind": "OBJECT", } }), vec![], )) ); } #[tokio::test] async fn test_introspection_specific_interface_type_name_and_kind() { let doc = r#" query IntrospectionDroidKindQuery { __type(name: "Character") { name kind } } "#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "__type": { "name": "Character", "kind": "INTERFACE", } }), vec![] )) ); } #[tokio::test] async fn test_introspection_documentation() { let doc = r#" query IntrospectionDroidDescriptionQuery { __type(name: "Droid") { name description } } "#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "__type": { "name": "Droid", "description": "A mechanical creature in the Star Wars universe.", }, }), vec![] )) ); } #[tokio::test] async fn test_introspection_directives() { let q = r#" query IntrospectionQuery { __schema { directives { name locations } } } "#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let result = crate::execute(q, None, &schema, &graphql_vars! {}, &database) .await .unwrap(); let expected = graphql_value!({ "__schema": { "directives": [ { "name": "deprecated", "locations": [ "FIELD_DEFINITION", "ENUM_VALUE", ], }, { "name": "include", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT", ], }, { "name": "skip", "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT", ], }, { "name": "specifiedBy", "locations": [ "SCALAR", ], }, ], }, }); assert_eq!(result, (expected, vec![])); } #[tokio::test] async fn test_introspection_possible_types() { let doc = r#" query IntrospectionDroidDescriptionQuery { __type(name: "Character") { possibleTypes { name } } } "#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let result = crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await; let (result, errors) = result.ok().expect("Query returned error"); assert_eq!(errors, vec![]); let possible_types = result .as_object_value() .expect("execution result not an object") .get_field_value("__type") .expect("'__type' not present in result") .as_object_value() .expect("'__type' not an object") .get_field_value("possibleTypes") .expect("'possibleTypes' not present in '__type'") .as_list_value() .expect("'possibleTypes' not a list") .iter() .map(|t| { t.as_object_value() .expect("possible type not an object") .get_field_value("name") .expect("'name' not present in type") .as_scalar_value::() .expect("'name' not a string") as &str }) .collect::>(); assert_eq!(possible_types, vec!["Human", "Droid"].into_iter().collect()); } #[tokio::test] async fn test_builtin_introspection_query() { let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let result = crate::introspect(&schema, &database, IntrospectionFormat::default()).unwrap(); let expected = schema_introspection_result(); assert_eq!(result, (expected, vec![])); } #[tokio::test] async fn test_builtin_introspection_query_without_descriptions() { let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let result = crate::introspect(&schema, &database, IntrospectionFormat::WithoutDescriptions).unwrap(); let expected = schema_introspection_result_without_descriptions(); assert_eq!(result, (expected, vec![])); } juniper-0.16.2/src/tests/mod.rs000064400000000000000000000003371046102023000144470ustar 00000000000000//! Library tests and fixtures pub mod fixtures; #[cfg(test)] mod introspection_tests; #[cfg(test)] mod query_tests; #[cfg(test)] mod schema_introspection; #[cfg(test)] mod subscriptions; #[cfg(test)] mod type_info_tests; juniper-0.16.2/src/tests/query_tests.rs000064400000000000000000000261151046102023000162610ustar 00000000000000use crate::{ graphql_value, graphql_vars, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, }; #[tokio::test] async fn test_hero_name() { let doc = r#"{ hero { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok((graphql_value!({"hero": {"name": "R2-D2"}}), vec![])), ); } #[tokio::test] async fn test_hero_field_order() { let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let doc = r#"{ hero { id name } }"#; assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": {"id": "2001", "name": "R2-D2"}}), vec![], )), ); let doc_reversed = r#"{ hero { name id } }"#; assert_eq!( crate::execute(doc_reversed, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": {"name": "R2-D2", "id": "2001"}}), vec![], )), ); } #[tokio::test] async fn test_hero_name_and_friends() { let doc = r#"{ hero { id name friends { name } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": { "id": "2001", "name": "R2-D2", "friends": [ {"name": "Luke Skywalker"}, {"name": "Han Solo"}, {"name": "Leia Organa"}, ], }}), vec![], )), ); } #[tokio::test] async fn test_hero_name_and_friends_and_friends_of_friends() { let doc = r#"{ hero { id name friends { name appearsIn friends { name } } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": { "id": "2001", "name": "R2-D2", "friends": [{ "name": "Luke Skywalker", "appearsIn": ["NEW_HOPE", "EMPIRE", "JEDI"], "friends": [ {"name": "Han Solo"}, {"name": "Leia Organa"}, {"name": "C-3PO"}, {"name": "R2-D2"}, ], }, { "name": "Han Solo", "appearsIn": ["NEW_HOPE", "EMPIRE", "JEDI"], "friends": [ {"name": "Luke Skywalker"}, {"name": "Leia Organa"}, {"name": "R2-D2"}, ], }, { "name": "Leia Organa", "appearsIn": ["NEW_HOPE", "EMPIRE", "JEDI"], "friends": [ {"name": "Luke Skywalker"}, {"name": "Han Solo"}, {"name": "C-3PO"}, {"name": "R2-D2"}, ], }], }}), vec![], )), ); } #[tokio::test] async fn test_query_name() { let doc = r#"{ human(id: "1000") { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"human": {"name": "Luke Skywalker"}}), vec![], )), ); } #[tokio::test] async fn test_query_alias_single() { let doc = r#"{ luke: human(id: "1000") { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok((graphql_value!({"luke": {"name": "Luke Skywalker"}}), vec![])), ); } #[tokio::test] async fn test_query_alias_multiple() { let doc = r#"{ luke: human(id: "1000") { name } leia: human(id: "1003") { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "luke": {"name": "Luke Skywalker"}, "leia": {"name": "Leia Organa"}, }), vec![], )), ); } #[tokio::test] async fn test_query_alias_multiple_with_fragment() { let doc = r#" query UseFragment { luke: human(id: "1000") { ...HumanFragment } leia: human(id: "1003") { ...HumanFragment } } fragment HumanFragment on Human { name homePlanet }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({ "luke": {"name": "Luke Skywalker", "homePlanet": "Tatooine"}, "leia": {"name": "Leia Organa", "homePlanet": "Alderaan"}, }), vec![], )), ); } #[tokio::test] async fn test_query_name_variable() { let doc = r#"query FetchSomeIDQuery($someId: String!) { human(id: $someId) { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let vars = graphql_vars! {"someId": "1000"}; assert_eq!( crate::execute(doc, None, &schema, &vars, &database).await, Ok(( graphql_value!({"human": {"name": "Luke Skywalker"}}), vec![], )), ); } #[tokio::test] async fn test_query_name_invalid_variable() { let doc = r#"query FetchSomeIDQuery($someId: String!) { human(id: $someId) { name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let vars = graphql_vars! {"someId": "some invalid id"}; assert_eq!( crate::execute(doc, None, &schema, &vars, &database).await, Ok((graphql_value!({ "human": null }), vec![])), ); } #[tokio::test] async fn test_query_friends_names() { let doc = r#"{ human(id: "1000") { friends { name } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"human": { "friends": [ {"name": "Han Solo"}, {"name": "Leia Organa"}, {"name": "C-3PO"}, {"name": "R2-D2"}, ], }}), vec![], )), ); } #[tokio::test] async fn test_query_inline_fragments_droid() { let doc = r#"query InlineFragments { hero { name __typename ...on Droid { primaryFunction } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": { "__typename": "Droid", "name": "R2-D2", "primaryFunction": "Astromech", }}), vec![], )), ); } #[tokio::test] async fn test_query_inline_fragments_human() { let doc = r#"query InlineFragments { hero(episode: EMPIRE) { __typename name } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"hero": { "__typename": "Human", "name": "Luke Skywalker", }}), vec![], )), ); } #[tokio::test] async fn test_object_typename() { let doc = r#"{ human(id: "1000") { __typename } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok((graphql_value!({"human": {"__typename": "Human"}}), vec![])), ); } #[tokio::test] async fn interface_inline_fragment_friends() { let doc = r#"{ human(id: "1002") { friends { name ... on Human { homePlanet } ... on Droid { primaryFunction } } } }"#; let database = Database::new(); let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); assert_eq!( crate::execute(doc, None, &schema, &graphql_vars! {}, &database).await, Ok(( graphql_value!({"human": { "friends": [ {"name": "Luke Skywalker", "homePlanet": "Tatooine"}, {"name": "Leia Organa", "homePlanet": "Alderaan"}, {"name": "R2-D2", "primaryFunction": "Astromech"}, ], }}), vec![], )) ); } juniper-0.16.2/src/tests/schema_introspection.rs000064400000000000000000002552521046102023000201200ustar 00000000000000use crate::value::Value; pub(crate) fn schema_introspection_result() -> Value { graphql_value!({ "__schema": { "description": null, "queryType": { "name": "Query" }, "mutationType": null, "subscriptionType": null, "types": [ { "kind": "ENUM", "name": "Episode", "description": null, "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "NEW_HOPE", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "EMPIRE", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "JEDI", "description": null, "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "ENUM", "name": "__DirectiveLocation", "description": null, "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "QUERY", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "MUTATION", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "SUBSCRIPTION", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "SCALAR", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_DEFINITION", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD_DEFINITION", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "VARIABLE_DEFINITION", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_SPREAD", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "INLINE_FRAGMENT", "description": null, "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM_VALUE", "description": null, "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "ENUM", "name": "__TypeKind", "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "SCALAR", "description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", "isDeprecated": false, "deprecationReason": null }, { "name": "OBJECT", "description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", "isDeprecated": false, "deprecationReason": null }, { "name": "INTERFACE", "description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", "isDeprecated": false, "deprecationReason": null }, { "name": "UNION", "description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.", "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM", "description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", "isDeprecated": false, "deprecationReason": null }, { "name": "INPUT_OBJECT", "description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.", "isDeprecated": false, "deprecationReason": null }, { "name": "LIST", "description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", "isDeprecated": false, "deprecationReason": null }, { "name": "NON_NULL", "description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "INTERFACE", "name": "Character", "description": "A character in the Star Wars Trilogy", "specifiedByUrl": null, "fields": [ { "name": "id", "description": "The id of the character", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "description": "The name of the character", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "description": "The friends of the character", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "description": "Which movies they appear in", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": [ { "kind": "OBJECT", "name": "Droid", "ofType": null }, { "kind": "OBJECT", "name": "Human", "ofType": null }, ] }, { "kind": "SCALAR", "name": "Boolean", "description": null, "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "SCALAR", "name": "String", "description": null, "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Droid", "description": "A mechanical creature in the Star Wars universe.", "specifiedByUrl": null, "fields": [ { "name": "id", "description": "The id of the droid", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "description": "The name of the droid", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "description": "The friends of the droid", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "description": "Which movies they appear in", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "primaryFunction", "description": "The primary function of the droid", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Human", "description": "A humanoid creature in the Star Wars universe.", "specifiedByUrl": null, "fields": [ { "name": "id", "description": "The id of the human", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "description": "The name of the human", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "description": "The friends of the human", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "description": "Which movies they appear in", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "homePlanet", "description": "The home planet of the human", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Query", "description": "The root query object of the schema", "specifiedByUrl": null, "fields": [ { "name": "human", "description": null, "args": [ { "name": "id", "description": "id of the human", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ], "type": { "kind": "OBJECT", "name": "Human", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "droid", "description": null, "args": [ { "name": "id", "description": null, "description": "id of the droid", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ], "type": { "kind": "OBJECT", "name": "Droid", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "hero", "description": null, "args": [ { "name": "episode", "description": "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode", "type": { "kind": "ENUM", "name": "Episode", "ofType": null }, "defaultValue": null } ], "type": { "kind": "INTERFACE", "name": "Character", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Directive", "description": null, "specifiedByUrl": null, "fields": [ { "name": "name", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "locations", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "isRepeatable", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "args", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "onOperation", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" }, { "name": "onFragment", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" }, { "name": "onField", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__EnumValue", "description": null, "specifiedByUrl": null, "fields": [ { "name": "name", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "isDeprecated", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "deprecationReason", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Field", "description": null, "specifiedByUrl": null, "fields": [ { "name": "name", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "args", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "type", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "isDeprecated", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "deprecationReason", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__InputValue", "description": null, "specifiedByUrl": null, "fields": [ { "name": "name", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "type", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "defaultValue", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Schema", "description": null, "specifiedByUrl": null, "fields": [ { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "types", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "queryType", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "mutationType", "description": null, "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "subscriptionType", "description": null, "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "directives", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Type", "description": null, "specifiedByUrl": null, "fields": [ { "name": "name", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "specifiedByUrl", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "kind", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "fields", "description": null, "args": [ { "name": "includeDeprecated", "description": null, "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "defaultValue": "false" } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "ofType", "description": null, "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "inputFields", "description": null, "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "interfaces", "description": null, "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "possibleTypes", "description": null, "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "enumValues", "description": null, "args": [ { "name": "includeDeprecated", "description": null, "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "defaultValue": "false" } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, ], "directives": [ { "name": "deprecated", "description": null, "isRepeatable": false, "locations": [ "FIELD_DEFINITION", "ENUM_VALUE" ], "args": [ { "name": "reason", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ] }, { "name": "include", "description": null, "isRepeatable": false, "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "defaultValue": null } ] }, { "name": "skip", "description": null, "isRepeatable": false, "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "defaultValue": null } ] }, { "name": "specifiedBy", "description": null, "isRepeatable": false, "locations": [ "SCALAR" ], "args": [ { "name": "url", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ] } ] } }) } pub(crate) fn schema_introspection_result_without_descriptions() -> Value { graphql_value!({ "__schema": { "queryType": { "name": "Query" }, "mutationType": null, "subscriptionType": null, "types": [ { "kind": "ENUM", "name": "Episode", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "NEW_HOPE", "isDeprecated": false, "deprecationReason": null }, { "name": "EMPIRE", "isDeprecated": false, "deprecationReason": null }, { "name": "JEDI", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "ENUM", "name": "__DirectiveLocation", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "QUERY", "isDeprecated": false, "deprecationReason": null }, { "name": "MUTATION", "isDeprecated": false, "deprecationReason": null }, { "name": "SUBSCRIPTION", "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD", "isDeprecated": false, "deprecationReason": null }, { "name": "SCALAR", "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_DEFINITION", "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD_DEFINITION", "isDeprecated": false, "deprecationReason": null }, { "name": "VARIABLE_DEFINITION", "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_SPREAD", "isDeprecated": false, "deprecationReason": null }, { "name": "INLINE_FRAGMENT", "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM_VALUE", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "ENUM", "name": "__TypeKind", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ { "name": "SCALAR", "isDeprecated": false, "deprecationReason": null }, { "name": "OBJECT", "isDeprecated": false, "deprecationReason": null }, { "name": "INTERFACE", "isDeprecated": false, "deprecationReason": null }, { "name": "UNION", "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM", "isDeprecated": false, "deprecationReason": null }, { "name": "INPUT_OBJECT", "isDeprecated": false, "deprecationReason": null }, { "name": "LIST", "isDeprecated": false, "deprecationReason": null }, { "name": "NON_NULL", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, { "kind": "INTERFACE", "name": "Character", "specifiedByUrl": null, "fields": [ { "name": "id", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": [ { "kind": "OBJECT", "name": "Droid", "ofType": null }, { "kind": "OBJECT", "name": "Human", "ofType": null }, ] }, { "kind": "SCALAR", "name": "Boolean", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "SCALAR", "name": "String", "specifiedByUrl": null, "fields": null, "inputFields": null, "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Droid", "specifiedByUrl": null, "fields": [ { "name": "id", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "primaryFunction", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Human", "specifiedByUrl": null, "fields": [ { "name": "id", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "name", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "friends", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "INTERFACE", "name": "Character", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "appearsIn", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "Episode", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "homePlanet", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [ { "kind": "INTERFACE", "name": "Character", "ofType": null } ], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Query", "specifiedByUrl": null, "fields": [ { "name": "human", "args": [ { "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ], "type": { "kind": "OBJECT", "name": "Human", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "droid", "args": [ { "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ], "type": { "kind": "OBJECT", "name": "Droid", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "hero", "args": [ { "name": "episode", "type": { "kind": "ENUM", "name": "Episode", "ofType": null }, "defaultValue": null } ], "type": { "kind": "INTERFACE", "name": "Character", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Directive", "specifiedByUrl": null, "fields": [ { "name": "name", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "locations", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "isRepeatable", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "args", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "onOperation", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" }, { "name": "onFragment", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" }, { "name": "onField", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": true, "deprecationReason": "Use the locations array instead" }, ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__EnumValue", "specifiedByUrl": null, "fields": [ { "name": "name", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "isDeprecated", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "deprecationReason", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Field", "specifiedByUrl": null, "fields": [ { "name": "name", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "args", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "type", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "isDeprecated", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "deprecationReason", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__InputValue", "specifiedByUrl": null, "fields": [ { "name": "name", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "type", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "defaultValue", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Schema", "specifiedByUrl": null, "fields": [ { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "types", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "queryType", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "mutationType", "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "subscriptionType", "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "directives", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "__Type", "specifiedByUrl": null, "fields": [ { "name": "name", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "specifiedByUrl", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "kind", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "fields", "args": [ { "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "defaultValue": "false" } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "ofType", "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "inputFields", "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "interfaces", "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "possibleTypes", "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { "name": "enumValues", "args": [ { "name": "includeDeprecated", "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "defaultValue": "false" } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, ], "directives": [ { "name": "deprecated", "isRepeatable": false, "locations": [ "FIELD_DEFINITION", "ENUM_VALUE" ], "args": [ { "name": "reason", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ] }, { "name": "include", "isRepeatable": false, "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "defaultValue": null } ] }, { "name": "skip", "isRepeatable": false, "locations": [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], "args": [ { "name": "if", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "defaultValue": null } ] }, { "name": "specifiedBy", "isRepeatable": false, "locations": [ "SCALAR" ], "args": [ { "name": "url", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null } ] } ] } }) } juniper-0.16.2/src/tests/subscriptions.rs000064400000000000000000000214261046102023000166010ustar 00000000000000use std::{iter, pin::Pin}; use futures::{stream, StreamExt as _}; use crate::{ graphql_object, graphql_subscription, graphql_value, http::GraphQLRequest, Context, DefaultScalarValue, EmptyMutation, ExecutionError, FieldError, GraphQLObject, Object, RootNode, Value, }; #[derive(Debug, Clone)] pub struct MyContext(i32); impl Context for MyContext {} #[derive(GraphQLObject)] #[graphql(description = "A humanoid creature in the Star Wars universe")] #[derive(Clone)] struct Human { id: String, name: String, home_planet: String, } struct MyQuery; #[graphql_object(context = MyContext)] impl MyQuery { fn test(&self) -> i32 { 0 // NOTICE: does not serve a purpose } } type Schema = RootNode<'static, MyQuery, EmptyMutation, MySubscription, DefaultScalarValue>; fn run(f: impl std::future::Future) -> O { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(f) } type HumanStream = Pin + Send>>; struct MySubscription; #[graphql_subscription(context = MyContext)] impl MySubscription { async fn async_human() -> HumanStream { Box::pin(stream::once(async { Human { id: "stream id".into(), name: "stream name".into(), home_planet: "stream home planet".into(), } })) } async fn error_human() -> Result { Err(FieldError::new( "handler error", graphql_value!("more details"), )) } async fn human_with_context(context: &MyContext) -> HumanStream { let context_val = context.0; Box::pin(stream::once(async move { Human { id: context_val.to_string(), name: context_val.to_string(), home_planet: context_val.to_string(), } })) } async fn human_with_args(id: String, name: String) -> HumanStream { Box::pin(stream::once(async { Human { id, name, home_planet: "default home planet".into(), } })) } } /// Create all variables, execute subscription /// and collect returned iterators. /// Panics if query is invalid (GraphQLError is returned) fn create_and_execute( query: String, ) -> Result< ( Vec, Vec, ExecutionError>>>, ), Vec>, > { let request = GraphQLRequest::new(query, None, None); let root_node = Schema::new(MyQuery, EmptyMutation::new(), MySubscription); let context = MyContext(2); let response = run(crate::http::resolve_into_stream( &request, &root_node, &context, )); assert!(response.is_ok()); let (values, errors) = response.unwrap(); if !errors.is_empty() { return Err(errors); } // cannot compare with `assert_eq` because // stream does not implement Debug let response_value_object = match values { Value::Object(o) => Some(o), _ => None, }; assert!(response_value_object.is_some()); let response_returned_object = response_value_object.unwrap(); let fields = response_returned_object.into_iter(); let mut names = vec![]; let mut collected_values = vec![]; for (name, stream_val) in fields { names.push(name.clone()); // since macro returns Value::Scalar(iterator) every time, // other variants may be skipped match stream_val { Value::Scalar(stream) => { let collected = run(stream.collect::>()); collected_values.push(collected); } _ => unreachable!(), } } Ok((names, collected_values)) } #[test] fn returns_requested_object() { let query = r#"subscription { asyncHuman(id: "1") { id name } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter( std::iter::from_fn(move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("stream id"))), 2 => Some(("name", graphql_value!("stream name"))), _ => None, } }), )))]]; assert_eq!(names, vec!["asyncHuman"]); assert_eq!(collected_values, expected_values); } #[test] fn returns_error() { let query = r#"subscription { errorHuman(id: "1") { id name } }"#; let response = create_and_execute(query.into()); assert!(response.is_err()); let returned_errors = response.err().unwrap(); let expected_error = ExecutionError::new( crate::parser::SourcePosition::new(23, 1, 8), &["errorHuman"], FieldError::new("handler error", graphql_value!("more details")), ); assert_eq!(returned_errors, vec![expected_error]); } #[test] fn can_access_context() { let query = r#"subscription { humanWithContext { id } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("2"))), _ => None, } }, ))))]]; assert_eq!(names, vec!["humanWithContext"]); assert_eq!(collected_values, expected_values); } #[test] fn resolves_typed_inline_fragments() { let query = r#"subscription { ... on MySubscription { asyncHuman(id: "32") { id } } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("stream id"))), _ => None, } }, ))))]]; assert_eq!(names, vec!["asyncHuman"]); assert_eq!(collected_values, expected_values); } #[test] fn resolves_nontyped_inline_fragments() { let query = r#"subscription { ... { asyncHuman(id: "32") { id } } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("stream id"))), _ => None, } }, ))))]]; assert_eq!(names, vec!["asyncHuman"]); assert_eq!(collected_values, expected_values); } #[test] fn can_access_arguments() { let query = r#"subscription { humanWithArgs(id: "123", name: "args name") { id name } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("123"))), 2 => Some(("name", graphql_value!("args name"))), _ => None, } }, ))))]]; assert_eq!(names, vec!["humanWithArgs"]); assert_eq!(collected_values, expected_values); } #[test] fn type_alias() { let query = r#"subscription { aliasedHuman: asyncHuman(id: "1") { id name } }"#; let (names, collected_values) = create_and_execute(query.into()).expect("Got error from stream"); let mut iterator_count = 0; let expected_values = vec![vec![Ok(Value::Object(Object::from_iter(iter::from_fn( move || { iterator_count += 1; match iterator_count { 1 => Some(("id", graphql_value!("stream id"))), 2 => Some(("name", graphql_value!("stream name"))), _ => None, } }, ))))]]; assert_eq!(names, vec!["aliasedHuman"]); assert_eq!(collected_values, expected_values); } juniper-0.16.2/src/tests/type_info_tests.rs000064400000000000000000000046721046102023000171140ustar 00000000000000use indexmap::IndexMap; use crate::{ executor::{ExecutionResult, Executor, Registry}, graphql_value, graphql_vars, schema::{meta::MetaType, model::RootNode}, types::{ base::{Arguments, GraphQLType, GraphQLValue}, scalars::{EmptyMutation, EmptySubscription}, }, value::ScalarValue, }; pub struct NodeTypeInfo { name: String, attribute_names: Vec, } pub struct Node { attributes: IndexMap, } impl GraphQLType for Node where S: ScalarValue, { fn name(info: &Self::TypeInfo) -> Option<&str> { Some(&info.name) } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = info .attribute_names .iter() .map(|name| registry.field::(name, &())) .collect::>(); registry .build_object_type::(info, &fields) .into_meta() } } impl GraphQLValue for Node where S: ScalarValue, { type Context = (); type TypeInfo = NodeTypeInfo; fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { >::name(info) } fn resolve_field( &self, _: &Self::TypeInfo, field_name: &str, _: &Arguments, executor: &Executor, ) -> ExecutionResult { executor.resolve(&(), &self.attributes.get(field_name).unwrap()) } } #[test] fn test_node() { let doc = r#" { foo, bar, baz }"#; let node_info = NodeTypeInfo { name: "MyNode".into(), attribute_names: vec!["foo".into(), "bar".into(), "baz".into()], }; let mut node = Node { attributes: IndexMap::new(), }; node.attributes.insert("foo".into(), "1".into()); node.attributes.insert("bar".into(), "2".into()); node.attributes.insert("baz".into(), "3".into()); let schema: RootNode<_, _, _> = RootNode::new_with_info( node, EmptyMutation::new(), EmptySubscription::new(), node_info, (), (), ); assert_eq!( crate::execute_sync(doc, None, &schema, &graphql_vars! {}, &()), Ok(( graphql_value!({ "foo": "1", "bar": "2", "baz": "3", }), vec![], )), ); } juniper-0.16.2/src/types/async_await.rs000064400000000000000000000350531046102023000161770ustar 00000000000000use std::future; use auto_enums::enum_derive; use crate::{ ast::Selection, executor::{ExecutionResult, Executor}, parser::Spanning, value::{DefaultScalarValue, Object, ScalarValue, Value}, }; use crate::BoxFuture; use super::base::{is_excluded, merge_key_into, Arguments, GraphQLType, GraphQLValue}; /// Extension of [`GraphQLValue`] trait with asynchronous queries/mutations resolvers. /// /// Convenience macros related to asynchronous queries/mutations expand into an implementation of /// this trait and [`GraphQLValue`] for the given type. pub trait GraphQLValueAsync: GraphQLValue + Sync where Self::TypeInfo: Sync, Self::Context: Sync, S: ScalarValue + Send + Sync, { /// Resolves the value of a single field on this [`GraphQLValueAsync`]. /// /// The `arguments` object contains all the specified arguments, with default values being /// substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][3]. /// /// # Panics /// /// The default implementation panics. /// /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_field_async<'a>( &'a self, _info: &'a Self::TypeInfo, _field_name: &'a str, _arguments: &'a Arguments, _executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { panic!( "GraphQLValueAsync::resolve_field_async() must be implemented by objects and \ interfaces", ); } /// Resolves this [`GraphQLValueAsync`] (being an [interface][1] or an [union][2]) into a /// concrete downstream [object][3] type. /// /// Tries to resolve this [`GraphQLValueAsync`] into the provided `type_name`. If the type /// matches, then passes the instance along to [`Executor::resolve`]. /// /// # Panics /// /// The default implementation panics. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Unions /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_into_type_async<'a>( &'a self, info: &'a Self::TypeInfo, type_name: &str, selection_set: Option<&'a [Selection<'a, S>]>, executor: &'a Executor<'a, 'a, Self::Context, S>, ) -> BoxFuture<'a, ExecutionResult> { if self.type_name(info).unwrap() == type_name { self.resolve_async(info, selection_set, executor) } else { panic!( "GraphQLValueAsync::resolve_into_type_async() must be implemented by unions and \ interfaces", ); } } /// Resolves the provided `selection_set` against this [`GraphQLValueAsync`]. /// /// For non-[object][3] types, the `selection_set` will be [`None`] and the value should simply /// be returned. /// /// For [objects][3], all fields in the `selection_set` should be resolved. The default /// implementation uses [`GraphQLValueAsync::resolve_field_async`] to resolve all fields, /// including those through a fragment expansion. /// /// Since the [GraphQL spec specifies][0] that errors during field processing should result in /// a null-value, this might return `Ok(Null)` in case of a failure. Errors are recorded /// internally. /// /// # Panics /// /// The default implementation panics, if `selection_set` is [`None`]. /// /// [0]: https://spec.graphql.org/October2021#sec-Handling-Field-Errors /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { if let Some(sel) = selection_set { Box::pin(async move { Ok(resolve_selection_set_into_async(self, info, sel, executor).await) }) } else { panic!( "GraphQLValueAsync::resolve_async() must be implemented by non-object output types", ); } } } /// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers. /// /// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementers, so /// doesn't require manual or code-generated implementation. pub trait GraphQLTypeAsync: GraphQLValueAsync + GraphQLType where Self::Context: Sync, Self::TypeInfo: Sync, S: ScalarValue + Send + Sync, { } impl GraphQLTypeAsync for T where T: GraphQLValueAsync + GraphQLType + ?Sized, T::Context: Sync, T::TypeInfo: Sync, S: ScalarValue + Send + Sync, { } // Wrapper function around resolve_selection_set_into_async_recursive. // This wrapper is necessary because async fns can not be recursive. fn resolve_selection_set_into_async<'a, 'e, T, S>( instance: &'a T, info: &'a T::TypeInfo, selection_set: &'e [Selection<'e, S>], executor: &'e Executor<'e, 'e, T::Context, S>, ) -> BoxFuture<'a, Value> where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, 'e: 'a, { Box::pin(resolve_selection_set_into_async_recursive( instance, info, selection_set, executor, )) } struct AsyncField { name: String, value: Option>, } enum AsyncValue { Field(AsyncField), Nested(Value), } pub(crate) async fn resolve_selection_set_into_async_recursive<'a, T, S>( instance: &'a T, info: &'a T::TypeInfo, selection_set: &'a [Selection<'a, S>], executor: &'a Executor<'a, 'a, T::Context, S>, ) -> Value where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { use futures::stream::{FuturesOrdered, StreamExt as _}; #[enum_derive(Future)] enum AsyncValueFuture { Field(A), FragmentSpread(B), InlineFragment1(C), InlineFragment2(D), } let mut object = Object::with_capacity(selection_set.len()); let mut async_values = FuturesOrdered::>::new(); let meta_type = executor .schema() .concrete_type_by_name( instance .type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) .expect("Type not found in schema"); for selection in selection_set { match *selection { Selection::Field(Spanning { item: ref f, ref span, }) => { if is_excluded(&f.directives, executor.variables()) { continue; } let response_name = f.alias.as_ref().unwrap_or(&f.name).item; if f.name.item == "__typename" { object.add_field( response_name, Value::scalar(instance.concrete_type_name(executor.context(), info)), ); continue; } let meta_field = meta_type.field_by_name(f.name.item).unwrap_or_else(|| { panic!( "Field {} not found on type {:?}", f.name.item, meta_type.name(), ) }); let exec_vars = executor.variables(); let sub_exec = executor.field_sub_executor( response_name, f.name.item, span.start, f.selection_set.as_ref().map(|v| &v[..]), ); let args = Arguments::new( f.arguments.as_ref().map(|m| { m.item .iter() .filter_map(|(k, v)| { let val = v.item.clone().into_const(exec_vars)?; Some((k.item, Spanning::new(v.span, val))) }) .collect() }), &meta_field.arguments, ); let pos = span.start; let is_non_null = meta_field.field_type.is_non_null(); let response_name = response_name.to_string(); async_values.push_back(AsyncValueFuture::Field(async move { // TODO: implement custom future type instead of // two-level boxing. let res = instance .resolve_field_async(info, f.name.item, &args, &sub_exec) .await; let value = match res { Ok(Value::Null) if is_non_null => None, Ok(v) => Some(v), Err(e) => { sub_exec.push_error_at(e, pos); if is_non_null { None } else { Some(Value::null()) } } }; AsyncValue::Field(AsyncField { name: response_name, value, }) })); } Selection::FragmentSpread(Spanning { item: ref spread, ref span, }) => { if is_excluded(&spread.directives, executor.variables()) { continue; } let fragment = &executor .fragment_by_name(spread.name.item) .expect("Fragment could not be found"); let sub_exec = executor.type_sub_executor( Some(fragment.type_condition.item), Some(&fragment.selection_set[..]), ); let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); let type_name = instance.type_name(info); if executor .schema() .is_named_subtype(&concrete_type_name, fragment.type_condition.item) || Some(fragment.type_condition.item) == type_name { let sub_result = instance .resolve_into_type_async( info, &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, ) .await; if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { async_values.push_back(AsyncValueFuture::FragmentSpread( future::ready(AsyncValue::Field(AsyncField { name: k, value: Some(v), })), )); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } } Selection::InlineFragment(Spanning { item: ref fragment, ref span, }) => { if is_excluded(&fragment.directives, executor.variables()) { continue; } let sub_exec = executor.type_sub_executor( fragment.type_condition.as_ref().map(|c| c.item), Some(&fragment.selection_set[..]), ); if let Some(ref type_condition) = fragment.type_condition { // Check whether the type matches the type condition. let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); if executor .schema() .is_named_subtype(&concrete_type_name, type_condition.item) { let sub_result = instance .resolve_into_type_async( info, &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, ) .await; if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { async_values.push_back(AsyncValueFuture::InlineFragment1( future::ready(AsyncValue::Field(AsyncField { name: k, value: Some(v), })), )); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } } else { async_values.push_back(AsyncValueFuture::InlineFragment2(async move { let value = resolve_selection_set_into_async( instance, info, &fragment.selection_set[..], &sub_exec, ) .await; AsyncValue::Nested(value) })); } } } } while let Some(item) = async_values.next().await { match item { AsyncValue::Field(AsyncField { name, value }) => { if let Some(value) = value { merge_key_into(&mut object, &name, value); } else { return Value::null(); } } AsyncValue::Nested(obj) => match obj { v @ Value::Null => { return v; } Value::Object(obj) => { for (k, v) in obj { merge_key_into(&mut object, &k, v); } } _ => unreachable!(), }, } } Value::Object(object) } juniper-0.16.2/src/types/base.rs000064400000000000000000000575041046102023000146140ustar 00000000000000use indexmap::IndexMap; use crate::{ ast::{Directive, FromInputValue, InputValue, Selection}, executor::{ExecutionResult, Executor, Registry, Variables}, parser::Spanning, schema::meta::{Argument, MetaType}, value::{DefaultScalarValue, Object, ScalarValue, Value}, FieldResult, GraphQLEnum, IntoFieldError, }; /// GraphQL type kind /// /// The GraphQL specification defines a number of type kinds - the meta type\ /// of a type. #[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)] #[graphql(name = "__TypeKind", internal)] pub enum TypeKind { /// ## Scalar types /// /// Scalar types appear as the leaf nodes of GraphQL queries. Strings,\ /// numbers, and booleans are the built in types, and while it's possible\ /// to define your own, it's relatively uncommon. Scalar, /// ## Object types /// /// The most common type to be implemented by users. Objects have fields\ /// and can implement interfaces. Object, /// ## Interface types /// /// Interface types are used to represent overlapping fields between\ /// multiple types, and can be queried for their concrete type. Interface, /// ## Union types /// /// Unions are similar to interfaces but can not contain any fields on\ /// their own. Union, /// ## Enum types /// /// Like scalars, enum types appear as the leaf nodes of GraphQL queries. Enum, /// ## Input objects /// /// Represents complex values provided in queries _into_ the system. InputObject, /// ## List types /// /// Represent lists of other types. This library provides implementations\ /// for vectors and slices, but other Rust types can be extended to serve\ /// as GraphQL lists. List, /// ## Non-null types /// /// In GraphQL, nullable types are the default. By putting a `!` after a\ /// type, it becomes non-nullable. NonNull, } /// Field argument container #[derive(Debug)] pub struct Arguments<'a, S = DefaultScalarValue> { args: Option>>>, } impl<'a, S> Arguments<'a, S> { #[doc(hidden)] pub fn new( mut args: Option>>>, meta_args: &'a Option>>, ) -> Self where S: Clone, { if meta_args.is_some() && args.is_none() { args = Some(IndexMap::new()); } if let (Some(args), Some(meta_args)) = (&mut args, meta_args) { for arg in meta_args { let arg_name = arg.name.as_str(); if args.get(arg_name).is_none() { if let Some(val) = arg.default_value.as_ref() { args.insert(arg_name, Spanning::unlocated(val.clone())); } } } } Self { args } } /// Gets an argument by the given `name` and converts it into the desired /// type. /// /// If the argument is found, or a default argument has been provided, the /// given [`InputValue`] will be converted into the type `T`. /// /// Returns [`None`] if an argument with such `name` is not present. /// /// # Errors /// /// If the [`FromInputValue`] conversion fails. pub fn get(&self, name: &str) -> FieldResult, S> where T: FromInputValue, T::Error: IntoFieldError, { self.args .as_ref() .and_then(|args| args.get(name)) .map(|spanning| &spanning.item) .map(InputValue::convert) .transpose() .map_err(IntoFieldError::into_field_error) } /// Gets a direct reference to the [`Spanning`] argument [`InputValue`]. pub fn get_input_value(&self, name: &str) -> Option<&Spanning>> { self.args.as_ref().and_then(|args| args.get(name)) } } /// Primary trait used to resolve GraphQL values. /// /// All the convenience macros ultimately expand into an implementation of this trait for the given /// type. The macros remove duplicated definitions of fields and arguments, and add type checks on /// all resolving functions automatically. This can all be done manually too. /// /// [`GraphQLValue`] provides _some_ convenience methods for you, in the form of optional trait /// methods. The `type_name` method is mandatory, but other than that, it depends on what type /// you're exposing: /// - [Scalars][4], [enums][5], [lists][6] and [non-null wrappers][7] only require `resolve`. /// - [Interfaces][1] and [objects][3] require `resolve_field` _or_ `resolve` if you want to /// implement a custom resolution logic (probably not). /// - [Interfaces][1] and [unions][2] require `resolve_into_type` and `concrete_type_name`. /// - [Input objects][8] do not require anything. /// /// # Object safety /// /// This trait is [object safe][11], therefore may be turned into a [trait object][12] and used for /// resolving GraphQL values even when a concrete Rust type is erased. /// /// # 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. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Unions /// [3]: https://spec.graphql.org/October2021#sec-Objects /// [4]: https://spec.graphql.org/October2021#sec-Scalars /// [5]: https://spec.graphql.org/October2021#sec-Enums /// [6]: https://spec.graphql.org/October2021#sec-List /// [7]: https://spec.graphql.org/October2021#sec-Non-Null /// [8]: https://spec.graphql.org/October2021#sec-Input-Objects /// [11]: https://doc.rust-lang.org/reference/items/traits.html#object-safety /// [12]: https://doc.rust-lang.org/reference/types/trait-object.html pub trait GraphQLValue where S: ScalarValue, { /// Context type for this [`GraphQLValue`]. /// /// It's threaded through a query execution to all affected nodes, and can be used to hold /// common data, e.g. database connections or request session information. type Context; /// Type that may carry additional schema information for this [`GraphQLValue`]. /// /// It can be used to implement a schema that is partly dynamic, meaning that it can use /// information that is not known at compile time, for instance by reading it from a /// configuration file at startup. type TypeInfo; /// Returns name of the [`GraphQLType`] exposed by this [`GraphQLValue`]. /// /// This function will be called multiple times during a query execution. It must _not_ perform /// any calculation and _always_ return the same value. /// /// Usually, it should just call a [`GraphQLType::name`] inside. fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str>; /// Resolves the value of a single field on this [`GraphQLValue`]. /// /// The `arguments` object contains all the specified arguments, with default values being /// substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][3]. /// /// # Panics /// /// The default implementation panics. /// /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_field( &self, _info: &Self::TypeInfo, _field_name: &str, _arguments: &Arguments, _executor: &Executor, ) -> ExecutionResult { panic!("GraphQLValue::resolve_field() must be implemented by objects and interfaces"); } /// Resolves this [`GraphQLValue`] (being an [interface][1] or an [union][2]) into a concrete /// downstream [object][3] type. /// /// Tries to resolve this [`GraphQLValue`] into the provided `type_name`. If the type matches, /// then passes the instance along to [`Executor::resolve`]. /// /// # Panics /// /// The default implementation panics. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Unions /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve_into_type( &self, info: &Self::TypeInfo, type_name: &str, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { if self.type_name(info).unwrap() == type_name { self.resolve(info, selection_set, executor) } else { panic!( "GraphQLValue::resolve_into_type() must be implemented by unions and interfaces" ); } } /// Returns the concrete [`GraphQLType`] name for this [`GraphQLValue`] being an [interface][1], /// an [union][2] or an [object][3]. /// /// # Panics /// /// The default implementation panics. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Unions /// [3]: https://spec.graphql.org/October2021#sec-Objects #[allow(unused_variables)] fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { panic!( "GraphQLValue::concrete_type_name() must be implemented by unions, interfaces \ and objects", ); } /// Resolves the provided `selection_set` against this [`GraphQLValue`]. /// /// For non-[object][3] types, the `selection_set` will be [`None`] and the value should simply /// be returned. /// /// For [objects][3], all fields in the `selection_set` should be resolved. The default /// implementation uses [`GraphQLValue::resolve_field`] to resolve all fields, including those /// through a fragment expansion. /// /// Since the [GraphQL spec specifies][0] that errors during field processing should result in /// a null-value, this might return `Ok(Null)` in case of a failure. Errors are recorded /// internally. /// /// # Panics /// /// The default implementation panics, if `selection_set` is [`None`]. /// /// [0]: https://spec.graphql.org/October2021#sec-Errors-and-Non-Nullability /// [3]: https://spec.graphql.org/October2021#sec-Objects fn resolve( &self, info: &Self::TypeInfo, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { if let Some(sel) = selection_set { let mut res = Object::with_capacity(sel.len()); Ok( if resolve_selection_set_into(self, info, sel, executor, &mut res) { Value::Object(res) } else { Value::null() }, ) } else { panic!("GraphQLValue::resolve() must be implemented by non-object output types"); } } } /// Primary trait used to expose Rust types in a GraphQL schema. /// /// All of the convenience macros ultimately expand into an implementation of /// this trait for the given type. This can all be done manually. /// /// # Example /// /// Manually deriving an [object][3] is straightforward, but tedious. This is the equivalent of the /// `User` object as shown in the example in the documentation root: /// ``` /// # use std::collections::HashMap; /// use juniper::{ /// meta::MetaType, Arguments, Context, DefaultScalarValue, Executor, ExecutionResult, /// FieldResult, GraphQLType, GraphQLValue, Registry, /// }; /// /// #[derive(Debug)] /// struct Database { users: HashMap } /// impl Context for Database {} /// /// #[derive(Debug)] /// struct User { id: String, name: String, friend_ids: Vec } /// /// impl GraphQLType for User { /// fn name(_: &()) -> Option<&'static str> { /// Some("User") /// } /// /// fn meta<'r>(_: &(), registry: &mut Registry<'r>) -> MetaType<'r> /// where DefaultScalarValue: 'r, /// { /// // First, we need to define all fields and their types on this type. /// // /// // If we need arguments, want to implement interfaces, or want to add documentation /// // strings, we can do it here. /// let fields = &[ /// registry.field::<&String>("id", &()), /// registry.field::<&String>("name", &()), /// registry.field::>("friends", &()), /// ]; /// registry.build_object_type::(&(), fields).into_meta() /// } /// } /// /// impl GraphQLValue for User { /// type Context = Database; /// type TypeInfo = (); /// /// fn type_name(&self, _: &()) -> Option<&'static str> { /// ::name(&()) /// } /// /// fn resolve_field( /// &self, /// info: &(), /// field_name: &str, /// args: &Arguments, /// executor: &Executor /// ) -> ExecutionResult /// { /// // Next, we need to match the queried field name. All arms of this match statement /// // return `ExecutionResult`, which makes it hard to statically verify that the type you /// // pass on to `executor.resolve*` actually matches the one that you defined in `meta()` /// // above. /// let database = executor.context(); /// match field_name { /// // Because scalars are defined with another `Context` associated type, you must use /// // `resolve_with_ctx` here to make the `executor` perform automatic type conversion /// // of its argument. /// "id" => executor.resolve_with_ctx(info, &self.id), /// "name" => executor.resolve_with_ctx(info, &self.name), /// /// // You pass a vector of `User` objects to `executor.resolve`, and it will determine /// // which fields of the sub-objects to actually resolve based on the query. /// // The `executor` instance keeps track of its current position in the query. /// "friends" => executor.resolve(info, /// &self.friend_ids.iter() /// .filter_map(|id| database.users.get(id)) /// .collect::>() /// ), /// /// // We can only reach this panic in two cases: either a mismatch between the defined /// // schema in `meta()` above, or a validation failed because of a this library bug. /// // /// // In either of those two cases, the only reasonable way out is to panic the thread. /// _ => panic!("Field {field_name} not found on type User"), /// } /// } /// } /// ``` /// /// [3]: https://spec.graphql.org/October2021#sec-Objects pub trait GraphQLType: GraphQLValue where S: ScalarValue, { /// Returns name of this [`GraphQLType`] to expose. /// /// This function will be called multiple times during schema construction. It must _not_ /// perform any calculation and _always_ return the same value. fn name(info: &Self::TypeInfo) -> Option<&str>; /// Returns [`MetaType`] representing this [`GraphQLType`]. fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r; } /// Resolver logic for queries'/mutations' selection set. /// Calls appropriate resolver method for each field or fragment found /// and then merges returned values into `result` or pushes errors to /// field's/fragment's sub executor. /// /// Returns false if any errors occurred and true otherwise. pub(crate) fn resolve_selection_set_into( instance: &T, info: &T::TypeInfo, selection_set: &[Selection], executor: &Executor, result: &mut Object, ) -> bool where T: GraphQLValue + ?Sized, S: ScalarValue, { let meta_type = executor .schema() .concrete_type_by_name( instance .type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) .expect("Type not found in schema"); for selection in selection_set { match *selection { Selection::Field(Spanning { item: ref f, ref span, }) => { if is_excluded(&f.directives, executor.variables()) { continue; } let response_name = f.alias.as_ref().unwrap_or(&f.name).item; if f.name.item == "__typename" { result.add_field( response_name, Value::scalar(instance.concrete_type_name(executor.context(), info)), ); continue; } let meta_field = meta_type.field_by_name(f.name.item).unwrap_or_else(|| { panic!( "Field {} not found on type {:?}", f.name.item, meta_type.name(), ) }); let exec_vars = executor.variables(); let sub_exec = executor.field_sub_executor( response_name, f.name.item, span.start, f.selection_set.as_ref().map(|v| &v[..]), ); let field_result = instance.resolve_field( info, f.name.item, &Arguments::new( f.arguments.as_ref().map(|m| { m.item .iter() .filter_map(|(k, v)| { let val = v.item.clone().into_const(exec_vars)?; Some((k.item, Spanning::new(v.span, val))) }) .collect() }), &meta_field.arguments, ), &sub_exec, ); match field_result { Ok(Value::Null) if meta_field.field_type.is_non_null() => return false, Ok(v) => merge_key_into(result, response_name, v), Err(e) => { sub_exec.push_error_at(e, span.start); if meta_field.field_type.is_non_null() { return false; } result.add_field(response_name, Value::null()); } } } Selection::FragmentSpread(Spanning { item: ref spread, span, }) => { if is_excluded(&spread.directives, executor.variables()) { continue; } let fragment = &executor .fragment_by_name(spread.name.item) .expect("Fragment could not be found"); let sub_exec = executor.type_sub_executor( Some(fragment.type_condition.item), Some(&fragment.selection_set[..]), ); let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); let type_name = instance.type_name(info); if executor .schema() .is_named_subtype(&concrete_type_name, fragment.type_condition.item) || Some(fragment.type_condition.item) == type_name { let sub_result = instance.resolve_into_type( info, &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, ); if let Ok(Value::Object(object)) = sub_result { for (k, v) in object { merge_key_into(result, &k, v); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } } Selection::InlineFragment(Spanning { item: ref fragment, ref span, }) => { if is_excluded(&fragment.directives, executor.variables()) { continue; } let sub_exec = executor.type_sub_executor( fragment.type_condition.as_ref().map(|c| c.item), Some(&fragment.selection_set[..]), ); if let Some(ref type_condition) = fragment.type_condition { // Check whether the type matches the type condition. let concrete_type_name = instance.concrete_type_name(sub_exec.context(), info); if executor .schema() .is_named_subtype(&concrete_type_name, type_condition.item) { let sub_result = instance.resolve_into_type( info, &concrete_type_name, Some(&fragment.selection_set[..]), &sub_exec, ); if let Ok(Value::Object(object)) = sub_result { for (k, v) in object { merge_key_into(result, &k, v); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } } else if !resolve_selection_set_into( instance, info, &fragment.selection_set[..], &sub_exec, result, ) { return false; } } } } true } pub(super) fn is_excluded( directives: &Option>>>, vars: &Variables, ) -> bool where S: ScalarValue, { if let Some(directives) = directives { for Spanning { item: directive, .. } in directives { let condition: bool = directive .arguments .iter() .flat_map(|m| m.item.get("if")) .filter_map(|v| v.item.clone().into_const(vars)?.convert().ok()) .next() .unwrap(); if (directive.name.item == "skip" && condition) || (directive.name.item == "include" && !condition) { return true; } } } false } /// Merges `response_name`/`value` pair into `result` pub(crate) fn merge_key_into(result: &mut Object, response_name: &str, value: Value) { if let Some(v) = result.get_mut_field_value(response_name) { match v { Value::Object(dest_obj) => { if let Value::Object(src_obj) = value { merge_maps(dest_obj, src_obj); } } Value::List(dest_list) => { if let Value::List(src_list) = value { dest_list.iter_mut().zip(src_list).for_each(|(d, s)| { if let Value::Object(d_obj) = d { if let Value::Object(s_obj) = s { merge_maps(d_obj, s_obj); } } }); } } _ => {} } return; } result.add_field(response_name, value); } /// Merges `src` object's fields into `dest` fn merge_maps(dest: &mut Object, src: Object) { for (key, value) in src { if dest.contains_field(&key) { merge_key_into(dest, &key, value); } else { dest.add_field(key, value); } } } juniper-0.16.2/src/types/containers.rs000064400000000000000000000634521046102023000160460ustar 00000000000000use std::{ mem::{self, MaybeUninit}, ptr, }; use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, FieldError, IntoFieldError, Registry}, schema::meta::MetaType, types::{ async_await::GraphQLValueAsync, base::{GraphQLType, GraphQLValue}, }, value::{ScalarValue, Value}, }; impl GraphQLType for Option where T: GraphQLType, S: ScalarValue, { fn name(_: &Self::TypeInfo) -> Option<&'static str> { None } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_nullable_type::(info).into_meta() } } impl GraphQLValue for Option where S: ScalarValue, T: GraphQLValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { None } fn resolve( &self, info: &Self::TypeInfo, _: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { match *self { Some(ref obj) => executor.resolve(info, obj), None => Ok(Value::null()), } } } impl GraphQLValueAsync for Option where T: GraphQLValueAsync, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, ExecutionResult> { let f = async move { let value = match self { Some(obj) => executor.resolve_into_value_async(info, obj).await, None => Value::null(), }; Ok(value) }; Box::pin(f) } } impl> FromInputValue for Option { type Error = T::Error; fn from_input_value(v: &InputValue) -> Result { match v { InputValue::Null => Ok(None), v => v.convert().map(Some), } } } impl> ToInputValue for Option { fn to_input_value(&self) -> InputValue { match self { Some(v) => v.to_input_value(), None => InputValue::Null, } } } impl GraphQLType for Vec where T: GraphQLType, S: ScalarValue, { fn name(_: &Self::TypeInfo) -> Option<&'static str> { None } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_list_type::(info, None).into_meta() } } impl GraphQLValue for Vec where T: GraphQLValue, S: ScalarValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { None } fn resolve( &self, info: &Self::TypeInfo, _: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { resolve_into_list(executor, info, self.iter()) } } impl GraphQLValueAsync for Vec where T: GraphQLValueAsync, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, ExecutionResult> { let f = resolve_into_list_async(executor, info, self.iter()); Box::pin(f) } } impl> FromInputValue for Vec { type Error = FromInputValueVecError; fn from_input_value(v: &InputValue) -> Result { match v { InputValue::List(l) => l .iter() .map(|i| i.item.convert().map_err(FromInputValueVecError::Item)) .collect(), // See "Input Coercion" on List types: // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null InputValue::Null => Err(FromInputValueVecError::Null), other => other .convert() .map(|e| vec![e]) .map_err(FromInputValueVecError::Item), } } } impl ToInputValue for Vec where T: ToInputValue, S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) } } /// Possible errors of converting [`InputValue`] into [`Vec`]. #[derive(Clone, Debug, Eq, PartialEq)] pub enum FromInputValueVecError where T: FromInputValue, S: ScalarValue, { /// [`InputValue`] cannot be [`Null`]. /// /// See ["Combining List and Non-Null" section of spec][1]. /// /// [`Null`]: [`InputValue::Null`] /// [1]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null Null, /// Error of converting [`InputValue::List`]'s item. Item(T::Error), } impl IntoFieldError for FromInputValueVecError where T: FromInputValue, T::Error: IntoFieldError, S: ScalarValue, { fn into_field_error(self) -> FieldError { match self { Self::Null => "Failed to convert into `Vec`: Value cannot be `null`".into(), Self::Item(s) => s.into_field_error(), } } } impl GraphQLType for [T] where S: ScalarValue, T: GraphQLType, { fn name(_: &Self::TypeInfo) -> Option<&'static str> { None } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_list_type::(info, None).into_meta() } } impl GraphQLValue for [T] where S: ScalarValue, T: GraphQLValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { None } fn resolve( &self, info: &Self::TypeInfo, _: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { resolve_into_list(executor, info, self.iter()) } } impl GraphQLValueAsync for [T] where T: GraphQLValueAsync, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, ExecutionResult> { let f = resolve_into_list_async(executor, info, self.iter()); Box::pin(f) } } impl<'a, T, S> ToInputValue for &'a [T] where T: ToInputValue, S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) } } impl GraphQLType for [T; N] where S: ScalarValue, T: GraphQLType, { fn name(_: &Self::TypeInfo) -> Option<&'static str> { None } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_list_type::(info, Some(N)).into_meta() } } impl GraphQLValue for [T; N] where S: ScalarValue, T: GraphQLValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { None } fn resolve( &self, info: &Self::TypeInfo, _: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { resolve_into_list(executor, info, self.iter()) } } impl GraphQLValueAsync for [T; N] where T: GraphQLValueAsync, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, ExecutionResult> { let f = resolve_into_list_async(executor, info, self.iter()); Box::pin(f) } } impl FromInputValue for [T; N] where T: FromInputValue, S: ScalarValue, { type Error = FromInputValueArrayError; fn from_input_value(v: &InputValue) -> Result { struct PartiallyInitializedArray { arr: [MaybeUninit; N], init_len: usize, no_drop: bool, } impl Drop for PartiallyInitializedArray { fn drop(&mut self) { if self.no_drop { return; } // Dropping a `MaybeUninit` does nothing, thus we need to drop // the initialized elements manually, otherwise we may introduce // a memory/resource leak if `T: Drop`. for elem in &mut self.arr[0..self.init_len] { // SAFETY: This is safe, because `self.init_len` represents // exactly the number of initialized elements. unsafe { ptr::drop_in_place(elem.as_mut_ptr()); } } } } match *v { InputValue::List(ref ls) => { if ls.len() != N { return Err(FromInputValueArrayError::WrongCount { actual: ls.len(), expected: N, }); } if N == 0 { // TODO: Use `mem::transmute` instead of // `mem::transmute_copy` below, once it's allowed // for const generics: // https://github.com/rust-lang/rust/issues/61956 // SAFETY: `mem::transmute_copy` is safe here, because we // check `N` to be `0`. It's no-op, actually. return Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) }); } // SAFETY: The reason we're using a wrapper struct implementing // `Drop` here is to be panic safe: // `T: FromInputValue` implementation is not // controlled by us, so calling `i.item.convert()` below // may cause a panic when our array is initialized only // partially. In such situation we need to drop already // initialized values to avoid possible memory/resource // leaks if `T: Drop`. let mut out = PartiallyInitializedArray:: { // SAFETY: The `.assume_init()` here is safe, because the // type we are claiming to have initialized here is // a bunch of `MaybeUninit`s, which do not require // any initialization. arr: unsafe { MaybeUninit::uninit().assume_init() }, init_len: 0, no_drop: false, }; let mut items = ls.iter().map(|i| i.item.convert()); for elem in &mut out.arr[..] { if let Some(i) = items .next() .transpose() .map_err(FromInputValueArrayError::Item)? { *elem = MaybeUninit::new(i); out.init_len += 1; } } // Do not drop collected `items`, because we're going to return // them. out.no_drop = true; // TODO: Use `mem::transmute` instead of `mem::transmute_copy` // below, once it's allowed for const generics: // https://github.com/rust-lang/rust/issues/61956 // SAFETY: `mem::transmute_copy` is safe here, because we have // exactly `N` initialized `items`. // Also, despite `mem::transmute_copy` copies the value, // we won't have a double-free when `T: Drop` here, // because original array elements are `MaybeUninit`, so // do nothing on `Drop`. Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) }) } // See "Input Coercion" on List types: // https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null InputValue::Null => Err(FromInputValueArrayError::Null), ref other => { other .convert() .map_err(FromInputValueArrayError::Item) .and_then(|e: T| { // TODO: Use `mem::transmute` instead of // `mem::transmute_copy` below, once it's allowed // for const generics: // https://github.com/rust-lang/rust/issues/61956 if N == 1 { // SAFETY: `mem::transmute_copy` is safe here, // because we check `N` to be `1`. // Also, despite `mem::transmute_copy` // copies the value, we won't have a // double-free when `T: Drop` here, because // original `e: T` value is wrapped into // `mem::ManuallyDrop`, so does nothing on // `Drop`. Ok(unsafe { mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)]) }) } else { Err(FromInputValueArrayError::WrongCount { actual: 1, expected: N, }) } }) } } } } impl ToInputValue for [T; N] where T: ToInputValue, S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) } } /// Error converting [`InputValue`] into exact-size [`array`](prim@array). #[derive(Clone, Debug, Eq, PartialEq)] pub enum FromInputValueArrayError where T: FromInputValue, S: ScalarValue, { /// [`InputValue`] cannot be [`Null`]. /// /// See ["Combining List and Non-Null" section of spec][1]. /// /// [`Null`]: [`InputValue::Null`] /// [1]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null Null, /// Wrong count of items. WrongCount { /// Actual count of items. actual: usize, /// Expected count of items. expected: usize, }, /// Error of converting [`InputValue::List`]'s item. Item(T::Error), } impl IntoFieldError for FromInputValueArrayError where T: FromInputValue, T::Error: IntoFieldError, S: ScalarValue, { fn into_field_error(self) -> FieldError { const ERROR_PREFIX: &str = "Failed to convert into exact-size array"; match self { Self::Null => format!("{ERROR_PREFIX}: Value cannot be `null`").into(), Self::WrongCount { actual, expected } => { format!("{ERROR_PREFIX}: wrong elements count: {actual} instead of {expected}",) .into() } Self::Item(s) => s.into_field_error(), } } } fn resolve_into_list<'t, S, T, I>( executor: &Executor, info: &T::TypeInfo, iter: I, ) -> ExecutionResult where S: ScalarValue, I: Iterator + ExactSizeIterator, T: GraphQLValue + ?Sized + 't, { let stop_on_null = executor .current_type() .list_contents() .expect("Current type is not a list type") .is_non_null(); let mut result = Vec::with_capacity(iter.len()); for o in iter { let val = executor.resolve(info, o)?; if stop_on_null && val.is_null() { return Ok(val); } else { result.push(val) } } Ok(Value::list(result)) } async fn resolve_into_list_async<'a, 't, S, T, I>( executor: &'a Executor<'a, 'a, T::Context, S>, info: &'a T::TypeInfo, items: I, ) -> ExecutionResult where I: Iterator + ExactSizeIterator, T: GraphQLValueAsync + ?Sized + 't, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { use futures::stream::{FuturesOrdered, StreamExt as _}; let stop_on_null = executor .current_type() .list_contents() .expect("Current type is not a list type") .is_non_null(); let mut futures = items .map(|it| async move { executor.resolve_into_value_async(info, it).await }) .collect::>(); let mut values = Vec::with_capacity(futures.len()); while let Some(value) = futures.next().await { if stop_on_null && value.is_null() { return Ok(value); } values.push(value); } Ok(Value::list(values)) } #[cfg(test)] mod coercion { use crate::{graphql_input_value, FromInputValue as _, InputValue, IntoFieldError as _}; use super::{FromInputValueArrayError, FromInputValueVecError}; type V = InputValue; #[test] fn option() { let v: V = graphql_input_value!(null); assert_eq!(>::from_input_value(&v), Ok(None)); let v: V = graphql_input_value!(1); assert_eq!(>::from_input_value(&v), Ok(Some(1))); } // See "Input Coercion" examples on List types: // https://spec.graphql.org/October2021/#sec-List.Input-Coercion #[test] fn vec() { let v: V = graphql_input_value!(null); assert_eq!( >::from_input_value(&v), Err(FromInputValueVecError::Null), ); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Null), ); assert_eq!(>>::from_input_value(&v), Ok(None)); assert_eq!(>>>::from_input_value(&v), Ok(None)); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Null), ); assert_eq!( >>>>>::from_input_value(&v), Ok(None), ); let v: V = graphql_input_value!(1); assert_eq!(>::from_input_value(&v), Ok(vec![1])); assert_eq!(>>::from_input_value(&v), Ok(vec![Some(1)])); assert_eq!(>>::from_input_value(&v), Ok(Some(vec![1]))); assert_eq!( >>>::from_input_value(&v), Ok(Some(vec![Some(1)])), ); assert_eq!(>>::from_input_value(&v), Ok(vec![vec![1]])); assert_eq!( >>>>>::from_input_value(&v), Ok(Some(vec![Some(vec![Some(1)])])), ); let v: V = graphql_input_value!([1, 2, 3]); assert_eq!(>::from_input_value(&v), Ok(vec![1, 2, 3])); assert_eq!( >>::from_input_value(&v), Ok(Some(vec![1, 2, 3])), ); assert_eq!( >>::from_input_value(&v), Ok(vec![Some(1), Some(2), Some(3)]), ); assert_eq!( >>>::from_input_value(&v), Ok(Some(vec![Some(1), Some(2), Some(3)])), ); assert_eq!( >>::from_input_value(&v), Ok(vec![vec![1], vec![2], vec![3]]), ); // Looks like the spec ambiguity. // See: https://github.com/graphql/graphql-spec/pull/515 assert_eq!( >>>>>::from_input_value(&v), Ok(Some(vec![ Some(vec![Some(1)]), Some(vec![Some(2)]), Some(vec![Some(3)]), ])), ); let v: V = graphql_input_value!([1, 2, null]); assert_eq!( >::from_input_value(&v), Err(FromInputValueVecError::Item( "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Item( "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( >>::from_input_value(&v), Ok(vec![Some(1), Some(2), None]), ); assert_eq!( >>>::from_input_value(&v), Ok(Some(vec![Some(1), Some(2), None])), ); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Item(FromInputValueVecError::Null)), ); // Looks like the spec ambiguity. // See: https://github.com/graphql/graphql-spec/pull/515 assert_eq!( >>>>>::from_input_value(&v), Ok(Some(vec![Some(vec![Some(1)]), Some(vec![Some(2)]), None])), ); } // See "Input Coercion" examples on List types: // https://spec.graphql.org/October2021#sec-List.Input-Coercion #[test] fn array() { let v: V = graphql_input_value!(null); assert_eq!( <[i32; 0]>::from_input_value(&v), Err(FromInputValueArrayError::Null), ); assert_eq!( <[i32; 1]>::from_input_value(&v), Err(FromInputValueArrayError::Null), ); assert_eq!( <[Option; 0]>::from_input_value(&v), Err(FromInputValueArrayError::Null), ); assert_eq!( <[Option; 1]>::from_input_value(&v), Err(FromInputValueArrayError::Null), ); assert_eq!(>::from_input_value(&v), Ok(None)); assert_eq!(>::from_input_value(&v), Ok(None)); assert_eq!(; 0]>>::from_input_value(&v), Ok(None)); assert_eq!(; 1]>>::from_input_value(&v), Ok(None)); assert_eq!( <[[i32; 1]; 1]>::from_input_value(&v), Err(FromInputValueArrayError::Null), ); assert_eq!( ; 1]>; 1]>>::from_input_value(&v), Ok(None), ); let v: V = graphql_input_value!(1); assert_eq!(<[i32; 1]>::from_input_value(&v), Ok([1])); assert_eq!( <[i32; 0]>::from_input_value(&v), Err(FromInputValueArrayError::WrongCount { expected: 0, actual: 1, }), ); assert_eq!(<[Option; 1]>::from_input_value(&v), Ok([Some(1)])); assert_eq!(>::from_input_value(&v), Ok(Some([1]))); assert_eq!( ; 1]>>::from_input_value(&v), Ok(Some([Some(1)])), ); assert_eq!(<[[i32; 1]; 1]>::from_input_value(&v), Ok([[1]])); assert_eq!( ; 1]>; 1]>>::from_input_value(&v), Ok(Some([Some([Some(1)])])), ); let v: V = graphql_input_value!([1, 2, 3]); assert_eq!(<[i32; 3]>::from_input_value(&v), Ok([1, 2, 3])); assert_eq!( >::from_input_value(&v), Ok(Some([1, 2, 3])), ); assert_eq!( <[Option; 3]>::from_input_value(&v), Ok([Some(1), Some(2), Some(3)]), ); assert_eq!( ; 3]>>::from_input_value(&v), Ok(Some([Some(1), Some(2), Some(3)])), ); assert_eq!(<[[i32; 1]; 3]>::from_input_value(&v), Ok([[1], [2], [3]])); // Looks like the spec ambiguity. // See: https://github.com/graphql/graphql-spec/pull/515 assert_eq!( ; 1]>; 3]>>::from_input_value(&v), Ok(Some([Some([Some(1)]), Some([Some(2)]), Some([Some(3)]),])), ); let v: V = graphql_input_value!([1, 2, null]); assert_eq!( <[i32; 3]>::from_input_value(&v), Err(FromInputValueArrayError::Item( "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( >::from_input_value(&v), Err(FromInputValueArrayError::Item( "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( <[Option; 3]>::from_input_value(&v), Ok([Some(1), Some(2), None]), ); assert_eq!( ; 3]>>::from_input_value(&v), Ok(Some([Some(1), Some(2), None])), ); assert_eq!( <[[i32; 1]; 3]>::from_input_value(&v), Err(FromInputValueArrayError::Item( FromInputValueArrayError::Null )), ); // Looks like the spec ambiguity. // See: https://github.com/graphql/graphql-spec/pull/515 assert_eq!( ; 1]>; 3]>>::from_input_value(&v), Ok(Some([Some([Some(1)]), Some([Some(2)]), None])), ); } } juniper-0.16.2/src/types/marker.rs000064400000000000000000000207221046102023000151530ustar 00000000000000//! Marker traits for GraphQL types. //! //! This module provide specialized types for GraphQL. To ensure that //! only specification compliant construct compile, these marker //! traits are used. Encountering an error where one of these traits //! is involved implies that the construct is not valid in GraphQL. use std::sync::Arc; use crate::{GraphQLType, ScalarValue}; /// Maker trait for [GraphQL objects][1]. /// /// This trait extends the [`GraphQLType`] and is only used to mark an [object][1]. During /// compile this addition information is required to prevent unwanted structure compiling. If an /// object requires this trait instead of the [`GraphQLType`], then it explicitly requires /// [GraphQL objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5] /// and [unions][6]) are not allowed. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Enums /// [4]: https://spec.graphql.org/October2021#sec-Interfaces /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects /// [6]: https://spec.graphql.org/October2021#sec-Unions pub trait GraphQLObject: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// /// [1]: https://spec.graphql.org/October2021 fn mark() {} } impl GraphQLObject for &T where T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLObject for Box where T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLObject for Arc where T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } /// Maker trait for [GraphQL interfaces][1]. /// /// This trait extends the [`GraphQLType`] and is only used to mark an [interface][1]. During /// compile this addition information is required to prevent unwanted structure compiling. If an /// object requires this trait instead of the [`GraphQLType`], then it explicitly requires /// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] /// and [unions][6]) are not allowed. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Enums /// [4]: https://spec.graphql.org/October2021#sec-Objects /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects /// [6]: https://spec.graphql.org/October2021#sec-Unions pub trait GraphQLInterface: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// /// [1]: https://spec.graphql.org/October2021 fn mark() {} } impl GraphQLInterface for &T where T: GraphQLInterface + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLInterface for Box where T: GraphQLInterface + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLInterface for Arc where T: GraphQLInterface + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } /// Maker trait for [GraphQL unions][1]. /// /// This trait extends the [`GraphQLType`] and is only used to mark an [union][1]. During compile /// this addition information is required to prevent unwanted structure compiling. If an object /// requires this trait instead of the [`GraphQLType`], then it explicitly requires /// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and /// [interfaces][6]) are not allowed. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Scalars /// [3]: https://spec.graphql.org/October2021#sec-Enums /// [4]: https://spec.graphql.org/October2021#sec-Objects /// [5]: https://spec.graphql.org/October2021#sec-Input-Objects /// [6]: https://spec.graphql.org/October2021#sec-Interfaces pub trait GraphQLUnion: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according /// to the [GraphQL specification][1]. /// /// [1]: https://spec.graphql.org/October2021 fn mark() {} } impl GraphQLUnion for &T where T: GraphQLUnion + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLUnion for Box where T: GraphQLUnion + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl GraphQLUnion for Arc where T: GraphQLUnion + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } /// Marker trait for types which can be used as output types. /// /// The GraphQL specification differentiates between input and output /// types. Each type which can be used as an output type should /// implement this trait. The specification defines enum, scalar, /// object, union, and interface as output types. pub trait IsOutputType: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types /// are used correctly according to the GraphQL specification. fn mark() {} } impl IsOutputType for &T where T: IsOutputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for Box where T: IsOutputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for Arc where T: IsOutputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for Option where T: IsOutputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for Vec where T: IsOutputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for [T] where T: IsOutputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for [T; N] where T: IsOutputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsOutputType for str where S: ScalarValue {} /// Marker trait for types which can be used as input types. /// /// The GraphQL specification differentiates between input and output /// types. Each type which can be used as an input type should /// implement this trait. The specification defines enum, scalar, and /// input object input types. pub trait IsInputType: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types /// are used correctly according to the GraphQL specification. fn mark() {} } impl IsInputType for &T where T: IsInputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for Box where T: IsInputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for Arc where T: IsInputType + ?Sized, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for Option where T: IsInputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for Vec where T: IsInputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for [T] where T: IsInputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for [T; N] where T: IsInputType, S: ScalarValue, { #[inline] fn mark() { T::mark() } } impl IsInputType for str where S: ScalarValue {} juniper-0.16.2/src/types/mod.rs000064400000000000000000000002641046102023000144500ustar 00000000000000pub mod async_await; pub mod base; pub mod containers; pub mod marker; pub mod name; pub mod nullable; pub mod pointers; pub mod scalars; pub mod subscriptions; pub mod utilities; juniper-0.16.2/src/types/name.rs000064400000000000000000000033111046102023000146050ustar 00000000000000use std::{ borrow::Borrow, error::Error, fmt::{Display, Formatter, Result as FmtResult}, str::FromStr, }; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Name(String); impl Name { pub fn is_valid(input: &str) -> bool { for (i, c) in input.chars().enumerate() { let is_valid = c.is_ascii_alphabetic() || c == '_' || (i > 0 && c.is_ascii_digit()); if !is_valid { return false; } } !input.is_empty() } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct NameParseError(String); impl Display for NameParseError { fn fmt(&self, f: &mut Formatter) -> FmtResult { self.0.fmt(f) } } impl Error for NameParseError { fn description(&self) -> &str { &self.0 } } impl FromStr for Name { type Err = NameParseError; fn from_str(s: &str) -> Result { if Name::is_valid(s) { Ok(Name(s.into())) } else { Err(NameParseError(format!( "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{s}\" does not", ))) } } } impl Borrow for Name { fn borrow(&self) -> &String { &self.0 } } impl Borrow for Name { fn borrow(&self) -> &str { &self.0 } } #[test] fn test_name_is_valid() { assert!(Name::is_valid("Foo")); assert!(Name::is_valid("foo42")); assert!(Name::is_valid("_Foo")); assert!(Name::is_valid("_Foo42")); assert!(Name::is_valid("_foo42")); assert!(Name::is_valid("_42Foo")); assert!(!Name::is_valid("42_Foo")); assert!(!Name::is_valid("Foo-42")); assert!(!Name::is_valid("Foo???")); } juniper-0.16.2/src/types/nullable.rs000064400000000000000000000220301046102023000154620ustar 00000000000000use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, schema::meta::MetaType, types::{ async_await::GraphQLValueAsync, base::{GraphQLType, GraphQLValue}, marker::IsInputType, }, value::{ScalarValue, Value}, }; /// `Nullable` can be used in situations where you need to distinguish between an implicitly and /// explicitly null input value. /// /// The GraphQL spec states that these two field calls are similar, but are not identical: /// /// ```graphql /// { /// field(arg: null) /// field /// } /// ``` /// /// The first has explicitly provided null to the argument “arg”, while the second has implicitly /// not provided a value to the argument “arg”. These two forms may be interpreted differently. For /// example, a mutation representing deleting a field vs not altering a field, respectively. /// /// In cases where you do not need to be able to distinguish between the two types of null, you /// should simply use `Option`. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Nullable { /// No value ImplicitNull, /// No value, explicitly specified to be null ExplicitNull, /// Some value `T` Some(T), } // Implemented manually to omit redundant `T: Default` trait bound, imposed by // `#[derive(Default)]`. impl Default for Nullable { fn default() -> Self { Self::ImplicitNull } } impl Nullable { /// Returns `true` if the nullable is a `ExplicitNull` value. #[inline] pub fn is_explicit_null(&self) -> bool { matches!(self, Self::ExplicitNull) } /// Returns `true` if the nullable is a `ImplicitNull` value. #[inline] pub fn is_implicit_null(&self) -> bool { matches!(self, Self::ImplicitNull) } /// Returns `true` if the nullable is a `Some` value. #[inline] pub fn is_some(&self) -> bool { matches!(self, Self::Some(_)) } /// Returns `true` if the nullable is not a `Some` value. #[inline] pub fn is_null(&self) -> bool { !matches!(self, Self::Some(_)) } /// Converts from `&mut Nullable` to `Nullable<&mut T>`. #[inline] pub fn as_mut(&mut self) -> Nullable<&mut T> { match *self { Self::Some(ref mut x) => Nullable::Some(x), Self::ImplicitNull => Nullable::ImplicitNull, Self::ExplicitNull => Nullable::ExplicitNull, } } /// Returns the contained `Some` value, consuming the `self` value. /// /// # Panics /// /// Panics if the value is not a `Some` with a custom panic message provided by `msg`. #[inline] #[track_caller] pub fn expect(self, msg: &str) -> T { self.some().expect(msg) } /// Returns the contained `Some` value or a provided default. #[inline] pub fn unwrap_or(self, default: T) -> T { self.some().unwrap_or(default) } /// Returns the contained `Some` value or computes it from a closure. #[inline] pub fn unwrap_or_else T>(self, f: F) -> T { self.some().unwrap_or_else(f) } /// Maps a `Nullable` to `Nullable` by applying a function to a contained value. #[inline] pub fn map U>(self, f: F) -> Nullable { match self { Self::Some(x) => Nullable::Some(f(x)), Self::ImplicitNull => Nullable::ImplicitNull, Self::ExplicitNull => Nullable::ExplicitNull, } } /// Applies a function to the contained value (if any), or returns the provided default (if /// not). #[inline] pub fn map_or U>(self, default: U, f: F) -> U { self.some().map_or(default, f) } /// Applies a function to the contained value (if any), or computes a default (if not). #[inline] pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { self.some().map_or_else(default, f) } /// Transforms the `Nullable` into a `Result`, mapping `Some(v)` to `Ok(v)` and /// `ImplicitNull` or `ExplicitNull` to `Err(err)`. #[inline] pub fn ok_or(self, err: E) -> Result { self.some().ok_or(err) } /// Transforms the `Nullable` into a `Result`, mapping `Some(v)` to `Ok(v)` and /// `ImplicitNull` or `ExplicitNull` to `Err(err())`. #[inline] pub fn ok_or_else E>(self, err: F) -> Result { self.some().ok_or_else(err) } /// Returns the nullable if it contains a value, otherwise returns `b`. #[inline] #[must_use] pub fn or(self, b: Self) -> Self { match self { Self::Some(_) => self, _ => b, } } /// Returns the nullable if it contains a value, otherwise calls `f` and /// returns the result. #[inline] #[must_use] pub fn or_else Nullable>(self, f: F) -> Nullable { match self { Self::Some(_) => self, _ => f(), } } /// Replaces the actual value in the nullable by the value given in parameter, returning the /// old value if present, leaving a `Some` in its place without deinitializing either one. #[inline] #[must_use] pub fn replace(&mut self, value: T) -> Self { std::mem::replace(self, Self::Some(value)) } /// Converts from `Nullable` to `Option`. pub fn some(self) -> Option { match self { Self::Some(v) => Some(v), _ => None, } } /// Converts from `Nullable` to `Option>`, mapping `Some(v)` to `Some(Some(v))`, /// `ExplicitNull` to `Some(None)`, and `ImplicitNull` to `None`. pub fn explicit(self) -> Option> { match self { Self::Some(v) => Some(Some(v)), Self::ExplicitNull => Some(None), Self::ImplicitNull => None, } } } impl Nullable<&T> { /// Maps a `Nullable<&T>` to a `Nullable` by copying the contents of the nullable. pub fn copied(self) -> Nullable { self.map(|&t| t) } } impl Nullable<&mut T> { /// Maps a `Nullable<&mut T>` to a `Nullable` by copying the contents of the nullable. pub fn copied(self) -> Nullable { self.map(|&mut t| t) } } impl Nullable<&T> { /// Maps a `Nullable<&T>` to a `Nullable` by cloning the contents of the nullable. pub fn cloned(self) -> Nullable { self.map(|t| t.clone()) } } impl Nullable<&mut T> { /// Maps a `Nullable<&mut T>` to a `Nullable` by cloning the contents of the nullable. pub fn cloned(self) -> Nullable { self.map(|t| t.clone()) } } impl GraphQLType for Nullable where T: GraphQLType, S: ScalarValue, { fn name(_: &Self::TypeInfo) -> Option<&'static str> { None } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_nullable_type::(info).into_meta() } } impl GraphQLValue for Nullable where S: ScalarValue, T: GraphQLValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { None } fn resolve( &self, info: &Self::TypeInfo, _: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { match *self { Self::Some(ref obj) => executor.resolve(info, obj), _ => Ok(Value::null()), } } } impl GraphQLValueAsync for Nullable where T: GraphQLValueAsync, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, _: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, ExecutionResult> { let f = async move { let value = match self { Self::Some(obj) => executor.resolve_into_value_async(info, obj).await, _ => Value::null(), }; Ok(value) }; Box::pin(f) } } impl> FromInputValue for Nullable { type Error = >::Error; fn from_input_value(v: &InputValue) -> Result { match v { &InputValue::Null => Ok(Self::ExplicitNull), v => v.convert().map(Self::Some), } } fn from_implicit_null() -> Result { Ok(Self::ImplicitNull) } } impl ToInputValue for Nullable where T: ToInputValue, S: ScalarValue, { fn to_input_value(&self) -> InputValue { match *self { Self::Some(ref v) => v.to_input_value(), _ => InputValue::null(), } } } impl IsInputType for Nullable where T: IsInputType, S: ScalarValue, { } juniper-0.16.2/src/types/pointers.rs000064400000000000000000000164421046102023000155410ustar 00000000000000use std::{fmt, sync::Arc}; use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, schema::meta::MetaType, types::{ async_await::GraphQLValueAsync, base::{Arguments, GraphQLType, GraphQLValue}, }, value::ScalarValue, BoxFuture, }; impl GraphQLType for Box where T: GraphQLType + ?Sized, S: ScalarValue, { fn name(info: &Self::TypeInfo) -> Option<&str> { T::name(info) } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { T::meta(info, registry) } } impl GraphQLValue for Box where T: GraphQLValue + ?Sized, S: ScalarValue, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { (**self).type_name(info) } fn resolve_into_type( &self, info: &Self::TypeInfo, name: &str, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve_into_type(info, name, selection_set, executor) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, args: &Arguments, executor: &Executor, ) -> ExecutionResult { (**self).resolve_field(info, field, args, executor) } fn resolve( &self, info: &Self::TypeInfo, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve(info, selection_set, executor) } } impl GraphQLValueAsync for Box where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { (**self).resolve_async(info, selection_set, executor) } } impl FromInputValue for Box where S: ScalarValue, T: FromInputValue, { type Error = T::Error; fn from_input_value(v: &InputValue) -> Result, Self::Error> { >::from_input_value(v).map(Box::new) } } impl ToInputValue for Box where S: fmt::Debug, T: ToInputValue, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() } } impl<'e, S, T> GraphQLType for &'e T where T: GraphQLType + ?Sized, S: ScalarValue, { fn name(info: &Self::TypeInfo) -> Option<&str> { T::name(info) } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { T::meta(info, registry) } } impl<'e, S, T> GraphQLValue for &'e T where S: ScalarValue, T: GraphQLValue + ?Sized, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { (**self).type_name(info) } fn resolve_into_type( &self, info: &Self::TypeInfo, name: &str, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve_into_type(info, name, selection_set, executor) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, args: &Arguments, executor: &Executor, ) -> ExecutionResult { (**self).resolve_field(info, field, args, executor) } fn resolve( &self, info: &Self::TypeInfo, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve(info, selection_set, executor) } } impl<'e, S, T> GraphQLValueAsync for &'e T where T: GraphQLValueAsync + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_field_async<'b>( &'b self, info: &'b Self::TypeInfo, field_name: &'b str, arguments: &'b Arguments, executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult> { (**self).resolve_field_async(info, field_name, arguments, executor) } fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { (**self).resolve_async(info, selection_set, executor) } } impl<'a, T, S> ToInputValue for &'a T where S: fmt::Debug, T: ToInputValue, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() } } impl GraphQLType for Arc where S: ScalarValue, T: GraphQLType + ?Sized, { fn name(info: &Self::TypeInfo) -> Option<&str> { T::name(info) } fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { T::meta(info, registry) } } impl GraphQLValue for Arc where S: ScalarValue, T: GraphQLValue + ?Sized, { type Context = T::Context; type TypeInfo = T::TypeInfo; fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { (**self).type_name(info) } fn resolve_into_type( &self, info: &Self::TypeInfo, name: &str, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve_into_type(info, name, selection_set, executor) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, args: &Arguments, executor: &Executor, ) -> ExecutionResult { (**self).resolve_field(info, field, args, executor) } fn resolve( &self, info: &Self::TypeInfo, selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { (**self).resolve(info, selection_set, executor) } } impl GraphQLValueAsync for Arc where T: GraphQLValueAsync + Send + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { (**self).resolve_async(info, selection_set, executor) } } impl FromInputValue for Arc where S: ScalarValue, T: FromInputValue, { type Error = T::Error; fn from_input_value(v: &InputValue) -> Result, Self::Error> { >::from_input_value(v).map(Arc::new) } } impl ToInputValue for Arc where S: fmt::Debug, T: ToInputValue, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() } } juniper-0.16.2/src/types/scalars.rs000064400000000000000000000400641046102023000153230ustar 00000000000000use std::{char, fmt, marker::PhantomData, ops::Deref, rc::Rc, thread::JoinHandle, u32}; use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, graphql_scalar, macros::reflect, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, types::{ async_await::GraphQLValueAsync, base::{GraphQLType, GraphQLValue}, subscriptions::GraphQLSubscriptionValue, }, value::{ParseScalarResult, ScalarValue, Value}, GraphQLScalar, }; /// An ID as defined by the GraphQL specification /// /// Represented as a string, but can be converted _to_ from an integer as well. #[derive(Clone, Debug, Deserialize, Eq, GraphQLScalar, PartialEq, Serialize)] #[graphql(parse_token(String, i32))] pub struct ID(String); impl ID { fn to_output(&self) -> Value { Value::scalar(self.0.clone()) } fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().as_ref().map(ToString::to_string)) .map(Self) .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) } } impl From for ID { fn from(s: String) -> ID { ID(s) } } impl ID { /// Construct a new ID from anything implementing `Into` pub fn new>(value: S) -> Self { ID(value.into()) } } impl Deref for ID { type Target = str; fn deref(&self) -> &str { &self.0 } } impl fmt::Display for ID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } #[graphql_scalar(with = impl_string_scalar)] type String = std::string::String; mod impl_string_scalar { use super::*; pub(super) fn to_output(v: &str) -> Value { Value::scalar(v.to_owned()) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {v}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); while let Some(ch) = char_iter.next() { match ch { '\\' => match char_iter.next() { Some('"') => { ret.push('"'); } Some('/') => { ret.push('/'); } Some('n') => { ret.push('\n'); } Some('r') => { ret.push('\r'); } Some('t') => { ret.push('\t'); } Some('\\') => { ret.push('\\'); } Some('f') => { ret.push('\u{000c}'); } Some('b') => { ret.push('\u{0008}'); } Some('u') => { ret.push(parse_unicode_codepoint(&mut char_iter)?); } Some(s) => { return Err(ParseError::LexerError(LexerError::UnknownEscapeSequence( format!("\\{s}"), ))) } None => return Err(ParseError::LexerError(LexerError::UnterminatedString)), }, ch => { ret.push(ch); } } } Ok(ret.into()) } else { Err(ParseError::unexpected_token(Token::Scalar(value))) } } } fn parse_unicode_codepoint(char_iter: &mut I) -> Result where I: Iterator, { let escaped_code_point = char_iter .next() .ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(String::from("\\u"))) }) .and_then(|c1| { char_iter .next() .map(|c2| format!("{c1}{c2}")) .ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{c1}"))) }) }) .and_then(|mut s| { char_iter .next() .ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}"))) }) .map(|c2| { s.push(c2); s }) }) .and_then(|mut s| { char_iter .next() .ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!("\\u{s}"))) }) .map(|c2| { s.push(c2); s }) })?; let code_point = u32::from_str_radix(&escaped_code_point, 16).map_err(|_| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( "\\u{escaped_code_point}", ))) })?; char::from_u32(code_point).ok_or_else(|| { ParseError::LexerError(LexerError::UnknownEscapeSequence(format!( "\\u{escaped_code_point}", ))) }) } impl reflect::WrappedType for str { const VALUE: reflect::WrappedValue = 1; } impl reflect::BaseType for str { const NAME: reflect::Type = "String"; } impl reflect::BaseSubTypes for str { const NAMES: reflect::Types = &[>::NAME]; } impl GraphQLType for str where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("String") } fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_scalar_type::(&()).into_meta() } } impl GraphQLValue for str where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { >::name(info) } fn resolve( &self, _: &(), _: Option<&[Selection]>, _: &Executor, ) -> ExecutionResult { Ok(Value::scalar(String::from(self))) } } impl GraphQLValueAsync for str where S: ScalarValue + Send + Sync, { fn resolve_async<'a>( &'a self, info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, ) -> crate::BoxFuture<'a, crate::ExecutionResult> { use futures::future; Box::pin(future::ready(self.resolve(info, selection_set, executor))) } } impl<'a, S> ToInputValue for &'a str where S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::scalar(String::from(*self)) } } #[graphql_scalar(with = impl_boolean_scalar)] type Boolean = bool; mod impl_boolean_scalar { use super::*; pub(super) fn to_output(v: &Boolean) -> Value { Value::scalar(*v) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_bool) .ok_or_else(|| format!("Expected `Boolean`, found: {v}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { // `Boolean`s are parsed separately, they shouldn't reach this code path. Err(ParseError::unexpected_token(Token::Scalar(value))) } } #[graphql_scalar(with = impl_int_scalar)] type Int = i32; mod impl_int_scalar { use super::*; pub(super) fn to_output(v: &Int) -> Value { Value::scalar(*v) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {v}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: i32| s.into()) } else { Err(ParseError::unexpected_token(Token::Scalar(value))) } } } #[graphql_scalar(with = impl_float_scalar)] type Float = f64; mod impl_float_scalar { use super::*; pub(super) fn to_output(v: &Float) -> Value { Value::scalar(*v) } pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {v}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { match value { ScalarToken::Int(v) => v .parse() .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: i32| f64::from(s).into()), ScalarToken::Float(v) => v .parse() .map_err(|_| ParseError::unexpected_token(Token::Scalar(value))) .map(|s: f64| s.into()), ScalarToken::String(_) => Err(ParseError::unexpected_token(Token::Scalar(value))), } } } /// Utility type to define read-only schemas /// /// If you instantiate `RootNode` with this as the mutation, no mutation will be /// generated for the schema. #[derive(Debug)] pub struct EmptyMutation(PhantomData>>); // `EmptyMutation` doesn't use `T`, so should be `Send` and `Sync` even when `T` is not. crate::sa::assert_impl_all!(EmptyMutation>: Send, Sync); impl EmptyMutation { /// Construct a new empty mutation #[inline] pub fn new() -> Self { Self(PhantomData) } } impl GraphQLType for EmptyMutation where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("_EmptyMutation") } fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_object_type::(&(), &[]).into_meta() } } impl GraphQLValue for EmptyMutation where S: ScalarValue, { type Context = T; type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { >::name(info) } } impl GraphQLValueAsync for EmptyMutation where Self::TypeInfo: Sync, Self::Context: Sync, S: ScalarValue + Send + Sync, { } // Implemented manually to omit redundant `T: Default` trait bound, imposed by // `#[derive(Default)]`. impl Default for EmptyMutation { fn default() -> Self { Self::new() } } /// Utillity type to define read-only schemas /// /// If you instantiate `RootNode` with this as the subscription, /// no subscriptions will be generated for the schema. pub struct EmptySubscription(PhantomData>>); // `EmptySubscription` doesn't use `T`, so should be `Send` and `Sync` even when `T` is not. crate::sa::assert_impl_all!(EmptySubscription>: Send, Sync); impl EmptySubscription { /// Construct a new empty subscription #[inline] pub fn new() -> Self { Self(PhantomData) } } impl GraphQLType for EmptySubscription where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("_EmptySubscription") } fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.build_object_type::(&(), &[]).into_meta() } } impl GraphQLValue for EmptySubscription where S: ScalarValue, { type Context = T; type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { >::name(info) } } impl GraphQLSubscriptionValue for EmptySubscription where Self::TypeInfo: Sync, Self::Context: Sync, S: ScalarValue + Send + Sync + 'static, { } // Implemented manually to omit redundant `T: Default` trait bound, imposed by // `#[derive(Default)]`. impl Default for EmptySubscription { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use crate::{ parser::ScalarToken, value::{DefaultScalarValue, ParseScalarValue}, }; use super::{EmptyMutation, EmptySubscription, ID}; #[test] fn test_id_from_string() { let actual = ID::from(String::from("foo")); let expected = ID(String::from("foo")); assert_eq!(actual, expected); } #[test] fn test_id_new() { let actual = ID::new("foo"); let expected = ID(String::from("foo")); assert_eq!(actual, expected); } #[test] fn test_id_deref() { let id = ID(String::from("foo")); assert_eq!(id.len(), 3); } #[test] fn test_id_display() { let id = ID("foo".into()); assert_eq!(id.to_string(), "foo"); } #[test] fn parse_strings() { fn parse_string(s: &str, expected: &str) { let s = >::from_str(ScalarToken::String(s)); assert!(s.is_ok(), "A parsing error occurred: {s:?}"); let s: Option = s.unwrap().into(); assert!(s.is_some(), "No string returned"); assert_eq!(s.unwrap(), expected); } parse_string("simple", "simple"); parse_string(" white space ", " white space "); parse_string(r#"quote \""#, "quote \""); parse_string(r"escaped \n\r\b\t\f", "escaped \n\r\u{0008}\t\u{000c}"); parse_string(r"slashes \\ \/", "slashes \\ /"); parse_string( r"unicode \u1234\u5678\u90AB\uCDEF", "unicode \u{1234}\u{5678}\u{90ab}\u{cdef}", ); } #[test] fn parse_f64_from_int() { for (v, expected) in [ ("0", 0), ("128", 128), ("1601942400", 1601942400), ("1696550400", 1696550400), ("-1", -1), ] { let n = >::from_str(ScalarToken::Int(v)); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); let n: Option = n.unwrap().into(); assert!(n.is_some(), "No `f64` returned"); assert_eq!(n.unwrap(), f64::from(expected)); } } #[test] fn parse_f64_from_float() { for (v, expected) in [ ("0.", 0.), ("1.2", 1.2), ("1601942400.", 1601942400.), ("1696550400.", 1696550400.), ("-1.2", -1.2), ] { let n = >::from_str(ScalarToken::Float(v)); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); let n: Option = n.unwrap().into(); assert!(n.is_some(), "No `f64` returned"); assert_eq!(n.unwrap(), expected); } } #[test] fn empty_mutation_is_send() { fn check_if_send() {} check_if_send::>(); } #[test] fn empty_subscription_is_send() { fn check_if_send() {} check_if_send::>(); } #[test] fn default_is_invariant_over_type() { struct Bar; let _ = EmptySubscription::::default(); let _ = EmptyMutation::::default(); } } juniper-0.16.2/src/types/subscriptions.rs000064400000000000000000000363201046102023000166020ustar 00000000000000use serde::Serialize; use crate::{ http::GraphQLRequest, parser::Spanning, types::base::{is_excluded, merge_key_into, GraphQLType, GraphQLValue}, Arguments, BoxFuture, DefaultScalarValue, ExecutionError, Executor, FieldError, Object, ScalarValue, Selection, Value, ValuesStream, }; /// Represents the result of executing a GraphQL operation (after parsing and validating has been /// done). #[derive(Debug, Serialize)] pub struct ExecutionOutput { /// The output data. pub data: Value, /// The errors that occurred. Note that the presence of errors does not mean there is no data. /// The output can have both data and errors. #[serde(bound(serialize = "S: ScalarValue"))] pub errors: Vec>, } impl ExecutionOutput { /// Creates execution output from data, with no errors. pub fn from_data(data: Value) -> Self { Self { data, errors: vec![], } } } /// Global subscription coordinator trait. /// /// With regular queries we could get away with not having some in-between /// layer, but for subscriptions it is needed, otherwise the integration crates /// can become really messy and cumbersome to maintain. Subscriptions are also /// quite a bit more stability sensitive than regular queries, they provide a /// great vector for DOS attacks and can bring down a server easily if not /// handled right. /// /// This trait implementation might include the following features: /// - contains the schema /// - keeps track of subscription connections /// - handles subscription start, maintains a global subscription id /// - max subscription limits / concurrency limits /// - subscription de-duplication /// - reconnection on connection loss / buffering / re-synchronisation /// /// /// `'a` is how long spawned connections live for. pub trait SubscriptionCoordinator<'a, CtxT, S> where S: ScalarValue, { /// Type of [`SubscriptionConnection`]s this [`SubscriptionCoordinator`] /// returns type Connection: SubscriptionConnection; /// Type of error while trying to spawn [`SubscriptionConnection`] type Error; /// Return [`SubscriptionConnection`] based on given [`GraphQLRequest`] fn subscribe( &'a self, _: &'a GraphQLRequest, _: &'a CtxT, ) -> BoxFuture<'a, Result>; } /// Single subscription connection. /// /// This trait implementation might: /// - hold schema + context /// - process subscribe, unsubscribe /// - unregister from coordinator upon close/shutdown /// - connection-local + global de-duplication, talk to coordinator /// - concurrency limits /// - machinery with coordinator to allow reconnection /// /// It can be treated as [`futures::Stream`] yielding [`GraphQLResponse`]s in /// server integration crates. /// /// [`GraphQLResponse`]: crate::http::GraphQLResponse pub trait SubscriptionConnection: futures::Stream> {} /// Extension of [`GraphQLValue`] trait with asynchronous [subscription][1] execution logic. /// It should be used with [`GraphQLValue`] in order to implement [subscription][1] resolvers on /// [GraphQL objects][2]. /// /// [Subscription][1]-related convenience macros expand into an implementation of this trait and /// [`GraphQLValue`] for the given type. /// /// See trait methods for more detailed explanation on how this trait works. /// /// [1]: https://spec.graphql.org/October2021#sec-Subscription /// [2]: https://spec.graphql.org/October2021#sec-Objects pub trait GraphQLSubscriptionValue: GraphQLValue + Sync where Self::TypeInfo: Sync, Self::Context: Sync, S: ScalarValue + Send + Sync, { /// Resolves into `Value`. /// /// ## Default implementation /// /// In order to resolve selection set on object types, default /// implementation calls `resolve_field_into_stream` every time a field /// needs to be resolved and `resolve_into_type_stream` every time a /// fragment needs to be resolved. /// /// For non-object types, the selection set will be `None` and default /// implementation will panic. fn resolve_into_stream<'s, 'i, 'ref_e, 'e, 'res, 'f>( &'s self, info: &'i Self::TypeInfo, executor: &'ref_e Executor<'ref_e, 'e, Self::Context, S>, ) -> BoxFuture<'f, Result>, FieldError>> where 'e: 'res, 'i: 'res, 's: 'f, 'ref_e: 'f, 'res: 'f, { if executor.current_selection_set().is_some() { Box::pin( async move { Ok(resolve_selection_set_into_stream(self, info, executor).await) }, ) } else { panic!("resolve_into_stream() must be implemented"); } } /// This method is called by Self's `resolve_into_stream` default /// implementation every time any field is found in selection set. /// /// It replaces `GraphQLValue::resolve_field`. /// Unlike `resolve_field`, which resolves each field into a single /// `Value`, this method resolves each field into /// `Value>`. /// /// The default implementation panics. fn resolve_field_into_stream<'s, 'i, 'ft, 'args, 'e, 'ref_e, 'res, 'f>( &'s self, _: &'i Self::TypeInfo, // this subscription's type info _: &'ft str, // field's type name _: Arguments<'args, S>, // field's arguments _: &'ref_e Executor<'ref_e, 'e, Self::Context, S>, // field's executor (subscription's sub-executor // with current field's selection set) ) -> BoxFuture<'f, Result>, FieldError>> where 's: 'f, 'i: 'res, 'ft: 'f, 'args: 'f, 'ref_e: 'f, 'res: 'f, 'e: 'res, { panic!("resolve_field_into_stream must be implemented"); } /// This method is called by Self's `resolve_into_stream` default /// implementation every time any fragment is found in selection set. /// /// It replaces `GraphQLValue::resolve_into_type`. /// Unlike `resolve_into_type`, which resolves each fragment /// a single `Value`, this method resolves each fragment into /// `Value>`. /// /// The default implementation panics. fn resolve_into_type_stream<'s, 'i, 'tn, 'e, 'ref_e, 'res, 'f>( &'s self, info: &'i Self::TypeInfo, // this subscription's type info type_name: &'tn str, // fragment's type name executor: &'ref_e Executor<'ref_e, 'e, Self::Context, S>, // fragment's executor (subscription's sub-executor // with current field's selection set) ) -> BoxFuture<'f, Result>, FieldError>> where 'i: 'res, 'e: 'res, 's: 'f, 'tn: 'f, 'ref_e: 'f, 'res: 'f, { Box::pin(async move { if self.type_name(info) == Some(type_name) { self.resolve_into_stream(info, executor).await } else { panic!("resolve_into_type_stream must be implemented"); } }) } } crate::sa::assert_obj_safe!(GraphQLSubscriptionValue); /// Extension of [`GraphQLType`] trait with asynchronous [subscription][1] execution logic. /// /// It's automatically implemented for [`GraphQLSubscriptionValue`] and [`GraphQLType`] /// implementers, so doesn't require manual or code-generated implementation. /// /// [1]: https://spec.graphql.org/October2021#sec-Subscription pub trait GraphQLSubscriptionType: GraphQLSubscriptionValue + GraphQLType where Self::Context: Sync, Self::TypeInfo: Sync, S: ScalarValue + Send + Sync, { } impl GraphQLSubscriptionType for T where T: GraphQLSubscriptionValue + GraphQLType + ?Sized, T::Context: Sync, T::TypeInfo: Sync, S: ScalarValue + Send + Sync, { } /// Wrapper function around `resolve_selection_set_into_stream_recursive`. /// This wrapper is necessary because async fns can not be recursive. /// Panics if executor's current selection set is None. pub(crate) fn resolve_selection_set_into_stream<'i, 'inf, 'ref_e, 'e, 'res, 'fut, T, S>( instance: &'i T, info: &'inf T::TypeInfo, executor: &'ref_e Executor<'ref_e, 'e, T::Context, S>, ) -> BoxFuture<'fut, Value>> where 'inf: 'res, 'e: 'res, 'i: 'fut, 'e: 'fut, 'ref_e: 'fut, 'res: 'fut, T: GraphQLSubscriptionValue + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, { Box::pin(resolve_selection_set_into_stream_recursive( instance, info, executor, )) } /// Selection set default resolver logic. /// Returns `Value::Null` if cannot keep resolving. Otherwise pushes errors to /// `Executor`. async fn resolve_selection_set_into_stream_recursive<'i, 'inf, 'ref_e, 'e, 'res, T, S>( instance: &'i T, info: &'inf T::TypeInfo, executor: &'ref_e Executor<'ref_e, 'e, T::Context, S>, ) -> Value> where T: GraphQLSubscriptionValue + ?Sized, T::TypeInfo: Sync, T::Context: Sync, S: ScalarValue + Send + Sync, 'inf: 'res, 'e: 'res, { let selection_set = executor .current_selection_set() .expect("Executor's selection set is none"); let mut object: Object> = Object::with_capacity(selection_set.len()); let meta_type = executor .schema() .concrete_type_by_name( instance .type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) .expect("Type not found in schema"); for selection in selection_set { match selection { Selection::Field(Spanning { item: ref f, ref span, }) => { if is_excluded(&f.directives, executor.variables()) { continue; } let response_name = f.alias.as_ref().unwrap_or(&f.name).item; let meta_field = meta_type .field_by_name(f.name.item) .unwrap_or_else(|| { panic!( "Field {} not found on type {:?}", f.name.item, meta_type.name(), ) }) .clone(); let exec_vars = executor.variables(); let sub_exec = executor.field_sub_executor( response_name, f.name.item, span.start, f.selection_set.as_ref().map(|x| &x[..]), ); let args = Arguments::new( f.arguments.as_ref().map(|m| { m.item .iter() .filter_map(|(k, v)| { let val = v.item.clone().into_const(exec_vars)?; Some((k.item, Spanning::new(v.span, val))) }) .collect() }), &meta_field.arguments, ); let is_non_null = meta_field.field_type.is_non_null(); let res = instance .resolve_field_into_stream(info, f.name.item, args, &sub_exec) .await; match res { Ok(Value::Null) if is_non_null => { return Value::Null; } Ok(v) => merge_key_into(&mut object, response_name, v), Err(e) => { sub_exec.push_error_at(e, span.start); if meta_field.field_type.is_non_null() { return Value::Null; } object.add_field(f.name.item, Value::Null); } } } Selection::FragmentSpread(Spanning { item: ref spread, ref span, }) => { if is_excluded(&spread.directives, executor.variables()) { continue; } let fragment = executor .fragment_by_name(spread.name.item) .expect("Fragment could not be found"); let sub_exec = executor.type_sub_executor( Some(fragment.type_condition.item), Some(&fragment.selection_set[..]), ); let obj = instance .resolve_into_type_stream(info, fragment.type_condition.item, &sub_exec) .await; match obj { Ok(val) => { match val { Value::Object(o) => { for (k, v) in o { merge_key_into(&mut object, &k, v); } } // since this was a wrapper of current function, // we'll rather get an object or nothing _ => unreachable!(), } } Err(e) => sub_exec.push_error_at(e, span.start), } } Selection::InlineFragment(Spanning { item: ref fragment, ref span, }) => { if is_excluded(&fragment.directives, executor.variables()) { continue; } let sub_exec = executor.type_sub_executor( fragment.type_condition.as_ref().map(|c| c.item), Some(&fragment.selection_set[..]), ); if let Some(ref type_condition) = fragment.type_condition { let sub_result = instance .resolve_into_type_stream(info, type_condition.item, &sub_exec) .await; if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { merge_key_into(&mut object, &k, v); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } else if let Some(type_name) = meta_type.name() { let sub_result = instance .resolve_into_type_stream(info, type_name, &sub_exec) .await; if let Ok(Value::Object(obj)) = sub_result { for (k, v) in obj { merge_key_into(&mut object, &k, v); } } else if let Err(e) = sub_result { sub_exec.push_error_at(e, span.start); } } else { return Value::Null; } } } } Value::Object(object) } juniper-0.16.2/src/types/utilities.rs000064400000000000000000000157751046102023000157210ustar 00000000000000use std::collections::HashSet; use crate::{ ast::InputValue, schema::{ meta::{Argument, EnumMeta, InputObjectMeta, MetaType}, model::{SchemaType, TypeType}, }, value::ScalarValue, }; /// Common error messages used in validation and execution of GraphQL operations pub(crate) mod error { use std::fmt::Display; pub(crate) fn non_null(arg_type: impl Display) -> String { format!("\"null\" specified for not nullable type \"{arg_type}\"") } pub(crate) fn enum_value(arg_value: impl Display, arg_type: impl Display) -> String { format!("Invalid value \"{arg_value}\" for enum \"{arg_type}\"") } pub(crate) fn type_value(arg_value: impl Display, arg_type: impl Display) -> String { format!("Invalid value \"{arg_value}\" for type \"{arg_type}\"") } pub(crate) fn parser(arg_type: impl Display, msg: impl Display) -> String { format!("Parser error for \"{arg_type}\": {msg}") } pub(crate) fn not_input_object(arg_type: impl Display) -> String { format!("\"{arg_type}\" is not an input object") } pub(crate) fn field( arg_type: impl Display, field_name: impl Display, error_message: impl Display, ) -> String { format!("Error on \"{arg_type}\" field \"{field_name}\": {error_message}") } pub(crate) fn missing_fields(arg_type: impl Display, missing_fields: impl Display) -> String { format!("\"{arg_type}\" is missing fields: {missing_fields}") } pub(crate) fn unknown_field(arg_type: impl Display, field_name: impl Display) -> String { format!("Field \"{field_name}\" does not exist on type \"{arg_type}\"") } pub(crate) fn invalid_list_length( arg_value: impl Display, actual: usize, expected: usize, ) -> String { format!("Expected list of length {expected}, but \"{arg_value}\" has length {actual}") } } /// Validates the specified field of a GraphQL object and returns an error message if the field is /// invalid. fn validate_object_field( schema: &SchemaType, object_type: &TypeType, object_fields: &[Argument], field_value: &InputValue, field_key: &str, ) -> Option where S: ScalarValue, { let field_type = object_fields .iter() .filter(|f| f.name == field_key) .map(|f| schema.make_type(&f.arg_type)) .next(); if let Some(field_arg_type) = field_type { let error_message = validate_literal_value(schema, &field_arg_type, field_value); error_message.map(|m| error::field(object_type, field_key, m)) } else { Some(error::unknown_field(object_type, field_key)) } } /// Validates the specified GraphQL literal and returns an error message if the it's invalid. pub fn validate_literal_value( schema: &SchemaType, arg_type: &TypeType, arg_value: &InputValue, ) -> Option where S: ScalarValue, { match *arg_type { TypeType::NonNull(ref inner) => { if arg_value.is_null() { Some(error::non_null(arg_type)) } else { validate_literal_value(schema, inner, arg_value) } } TypeType::List(ref inner, expected_size) => match *arg_value { InputValue::Null | InputValue::Variable(_) => None, InputValue::List(ref items) => { if let Some(expected) = expected_size { if items.len() != expected { return Some(error::invalid_list_length(arg_value, items.len(), expected)); } } items .iter() .find_map(|i| validate_literal_value(schema, inner, &i.item)) } ref v => { if let Some(expected) = expected_size { if expected != 1 { return Some(error::invalid_list_length(arg_value, 1, expected)); } } validate_literal_value(schema, inner, v) } }, TypeType::Concrete(t) => { // Even though InputValue::String can be parsed into an enum, they // are not valid as enum *literals* in a GraphQL query. if let (&InputValue::Scalar(_), Some(&MetaType::Enum(EnumMeta { .. }))) = (arg_value, arg_type.to_concrete()) { return Some(error::enum_value(arg_value, arg_type)); } match *arg_value { InputValue::Null | InputValue::Variable(_) => None, ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => { if let Some(parse_fn) = t.input_value_parse_fn() { if parse_fn(v).is_ok() { None } else { Some(error::type_value(arg_value, arg_type)) } } else { Some(error::parser(arg_type, "no parser present")) } } InputValue::List(_) => Some("Input lists are not literals".to_owned()), InputValue::Object(ref obj) => { if let MetaType::InputObject(InputObjectMeta { ref input_fields, .. }) = *t { let mut remaining_required_fields = input_fields .iter() .filter_map(|f| { (f.arg_type.is_non_null() && f.default_value.is_none()) .then_some(&f.name) }) .collect::>(); let error_message = obj.iter().find_map(|(key, value)| { remaining_required_fields.remove(&key.item); validate_object_field( schema, arg_type, input_fields, &value.item, &key.item, ) }); if error_message.is_some() { return error_message; } if remaining_required_fields.is_empty() { None } else { let missing_fields = remaining_required_fields .into_iter() .map(|s| format!("\"{}\"", &**s)) .collect::>() .join(", "); Some(error::missing_fields(arg_type, missing_fields)) } } else { Some(error::not_input_object(arg_type)) } } } } } } juniper-0.16.2/src/util.rs000064400000000000000000000027411046102023000135040ustar 00000000000000use std::borrow::Cow; /// Convert string to camel case. /// /// Note: needs to be public because several macros use it. #[doc(hidden)] pub fn to_camel_case(s: &'_ str) -> Cow<'_, str> { let mut dest = Cow::Borrowed(s); // handle '_' to be more friendly with the // _var convention for unused variables let s_iter = if let Some(stripped) = s.strip_prefix('_') { stripped } else { s } .split('_') .enumerate(); for (i, part) in s_iter { if i > 0 && part.len() == 1 { dest += Cow::Owned(part.to_uppercase()); } else if i > 0 && part.len() > 1 { let first = part .chars() .next() .unwrap() .to_uppercase() .collect::(); let second = &part[1..]; dest += Cow::Owned(first); dest += second; } else if i == 0 { dest = Cow::Borrowed(part); } } dest } #[test] fn test_to_camel_case() { assert_eq!(&to_camel_case("test")[..], "test"); assert_eq!(&to_camel_case("_test")[..], "test"); assert_eq!(&to_camel_case("first_second")[..], "firstSecond"); assert_eq!(&to_camel_case("first_")[..], "first"); assert_eq!(&to_camel_case("a_b_c")[..], "aBC"); assert_eq!(&to_camel_case("a_bc")[..], "aBc"); assert_eq!(&to_camel_case("a_b")[..], "aB"); assert_eq!(&to_camel_case("a")[..], "a"); assert_eq!(&to_camel_case("")[..], ""); } juniper-0.16.2/src/validation/context.rs000064400000000000000000000124641046102023000163500ustar 00000000000000use std::{ collections::HashSet, fmt::{self, Debug}, }; use crate::ast::{Definition, Document, Type}; use crate::schema::{meta::MetaType, model::SchemaType}; use crate::parser::SourcePosition; /// Query validation error #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct RuleError { locations: Vec, message: String, } #[doc(hidden)] pub struct ValidatorContext<'a, S: Debug + 'a> { pub schema: &'a SchemaType<'a, S>, errors: Vec, type_stack: Vec>>, type_literal_stack: Vec>>, input_type_stack: Vec>>, input_type_literal_stack: Vec>>, parent_type_stack: Vec>>, fragment_names: HashSet<&'a str>, } impl RuleError { #[doc(hidden)] pub fn new(message: &str, locations: &[SourcePosition]) -> Self { Self { message: message.into(), locations: locations.to_vec(), } } /// Access the message for a validation error pub fn message(&self) -> &str { &self.message } /// Access the positions of the validation error /// /// All validation errors contain at least one source position, but some /// validators supply extra context through multiple positions. pub fn locations(&self) -> &[SourcePosition] { &self.locations } } impl fmt::Display for RuleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // This is fine since all `RuleError`s should have at least one source // position. let locations = self .locations .iter() .map(ToString::to_string) .collect::>() .join(", "); write!(f, "{}. At {locations}", self.message) } } impl std::error::Error for RuleError {} impl<'a, S: Debug> ValidatorContext<'a, S> { #[doc(hidden)] pub fn new(schema: &'a SchemaType, document: &Document<'a, S>) -> ValidatorContext<'a, S> { ValidatorContext { errors: Vec::new(), schema, type_stack: Vec::new(), type_literal_stack: Vec::new(), parent_type_stack: Vec::new(), input_type_stack: Vec::new(), input_type_literal_stack: Vec::new(), fragment_names: document .iter() .filter_map(|def| match *def { Definition::Fragment(ref frag) => Some(frag.item.name.item), _ => None, }) .collect(), } } #[doc(hidden)] pub fn append_errors(&mut self, mut errors: Vec) { self.errors.append(&mut errors); } #[doc(hidden)] pub fn report_error(&mut self, message: &str, locations: &[SourcePosition]) { self.errors.push(RuleError::new(message, locations)) } pub(crate) fn has_errors(&self) -> bool { !self.errors.is_empty() } #[doc(hidden)] pub fn into_errors(mut self) -> Vec { self.errors.sort(); self.errors } #[doc(hidden)] pub fn with_pushed_type(&mut self, t: Option<&Type<'a>>, f: F) -> R where F: FnOnce(&mut ValidatorContext<'a, S>) -> R, { if let Some(t) = t { self.type_stack .push(self.schema.concrete_type_by_name(t.innermost_name())); } else { self.type_stack.push(None); } self.type_literal_stack.push(t.cloned()); let res = f(self); self.type_literal_stack.pop(); self.type_stack.pop(); res } #[doc(hidden)] pub fn with_pushed_parent_type(&mut self, f: F) -> R where F: FnOnce(&mut ValidatorContext<'a, S>) -> R, { self.parent_type_stack .push(*self.type_stack.last().unwrap_or(&None)); let res = f(self); self.parent_type_stack.pop(); res } #[doc(hidden)] pub fn with_pushed_input_type(&mut self, t: Option<&Type<'a>>, f: F) -> R where F: FnOnce(&mut ValidatorContext<'a, S>) -> R, { if let Some(t) = t { self.input_type_stack .push(self.schema.concrete_type_by_name(t.innermost_name())); } else { self.input_type_stack.push(None); } self.input_type_literal_stack.push(t.cloned()); let res = f(self); self.input_type_literal_stack.pop(); self.input_type_stack.pop(); res } #[doc(hidden)] pub fn current_type(&self) -> Option<&'a MetaType<'a, S>> { *self.type_stack.last().unwrap_or(&None) } #[doc(hidden)] pub fn current_type_literal(&self) -> Option<&Type<'a>> { match self.type_literal_stack.last() { Some(Some(t)) => Some(t), _ => None, } } #[doc(hidden)] pub fn parent_type(&self) -> Option<&'a MetaType<'a, S>> { *self.parent_type_stack.last().unwrap_or(&None) } #[doc(hidden)] pub fn current_input_type_literal(&self) -> Option<&Type<'a>> { match self.input_type_literal_stack.last() { Some(Some(t)) => Some(t), _ => None, } } #[doc(hidden)] pub fn is_known_fragment(&self, name: &str) -> bool { self.fragment_names.contains(name) } } juniper-0.16.2/src/validation/input_value.rs000064400000000000000000000257031046102023000172170ustar 00000000000000use std::{collections::HashSet, fmt}; use crate::{ ast::{InputValue, Operation, VariableDefinitions}, executor::Variables, parser::{SourcePosition, Spanning}, schema::{ meta::{EnumMeta, InputObjectMeta, MetaType, ScalarMeta}, model::{SchemaType, TypeType}, }, validation::RuleError, value::ScalarValue, }; #[derive(Debug)] enum Path<'a> { Root, ArrayElement(usize, &'a Path<'a>), ObjectField(&'a str, &'a Path<'a>), } #[doc(hidden)] pub fn validate_input_values( values: &Variables, operation: &Spanning>, schema: &SchemaType, ) -> Vec where S: ScalarValue, { let mut errs = vec![]; if let Some(ref vars) = operation.item.variable_definitions { validate_var_defs(values, &vars.item, schema, &mut errs); } errs.sort(); errs } fn validate_var_defs( values: &Variables, var_defs: &VariableDefinitions, schema: &SchemaType, errors: &mut Vec, ) where S: ScalarValue, { for (name, def) in var_defs.iter() { let raw_type_name = def.var_type.item.innermost_name(); match schema.concrete_type_by_name(raw_type_name) { Some(t) if t.is_input() => { let ct = schema.make_type(&def.var_type.item); if def.var_type.item.is_non_null() && is_absent_or_null(values.get(name.item)) { errors.push(RuleError::new( &format!( r#"Variable "${}" of required type "{}" was not provided."#, name.item, def.var_type.item, ), &[name.span.start], )); } else if let Some(v) = values.get(name.item) { errors.append(&mut unify_value( name.item, &name.span.start, v, &ct, schema, Path::Root, )); } } _ => unreachable!( r#"Variable "${}" has invalid input type "{}" after document validation."#, name.item, def.var_type.item, ), } } } fn unify_value<'a, S>( var_name: &str, var_pos: &SourcePosition, value: &InputValue, meta_type: &TypeType<'a, S>, schema: &SchemaType, path: Path<'a>, ) -> Vec where S: ScalarValue, { let mut errors: Vec = vec![]; match *meta_type { TypeType::NonNull(ref inner) => { if value.is_null() { errors.push(unification_error( var_name, var_pos, &path, format!(r#"Expected "{meta_type}", found null"#), )); } else { errors.append(&mut unify_value( var_name, var_pos, value, inner, schema, path, )); } } TypeType::List(ref inner, expected_size) => { if value.is_null() { return errors; } match value.to_list_value() { Some(l) => { if let Some(expected) = expected_size { if l.len() != expected { errors.push(unification_error( var_name, var_pos, &path, format!( "Expected list of {expected} elements, \ found {} elements", l.len(), ), )); } } for (i, v) in l.iter().enumerate() { errors.append(&mut unify_value( var_name, var_pos, v, inner, schema, Path::ArrayElement(i, &path), )); } } _ => errors.append(&mut unify_value( var_name, var_pos, value, inner, schema, path, )), } } TypeType::Concrete(mt) => { if value.is_null() { return errors; } match *mt { MetaType::Scalar(ref sm) => { errors.append(&mut unify_scalar(var_name, var_pos, value, sm, &path)) } MetaType::Enum(ref em) => { errors.append(&mut unify_enum(var_name, var_pos, value, em, &path)) } MetaType::InputObject(ref iom) => { let mut e = unify_input_object(var_name, var_pos, value, iom, schema, &path); if e.is_empty() { // All the fields didn't have errors, see if there is an // overall error when parsing the input value. if let Err(e) = (iom.try_parse_fn)(value) { errors.push(unification_error( var_name, var_pos, &path, format!( "Expected input of type `{}`. \ Got: `{value}`. \ Details: {}", iom.name, e.message(), ), )); } } else { errors.append(&mut e); } } _ => panic!("Can't unify non-input concrete type"), } } } errors } fn unify_scalar( var_name: &str, var_pos: &SourcePosition, value: &InputValue, meta: &ScalarMeta, path: &Path<'_>, ) -> Vec where S: ScalarValue, { let mut errors: Vec = vec![]; if let Err(e) = (meta.try_parse_fn)(value) { return vec![unification_error( var_name, var_pos, path, format!( "Expected input scalar `{}`. Got: `{value}`. Details: {}", meta.name, e.message(), ), )]; } match *value { InputValue::List(_) => errors.push(unification_error( var_name, var_pos, path, format!(r#"Expected "{}", found list"#, meta.name), )), InputValue::Object(_) => errors.push(unification_error( var_name, var_pos, path, format!(r#"Expected "{}", found object"#, meta.name), )), _ => (), } errors } fn unify_enum( var_name: &str, var_pos: &SourcePosition, value: &InputValue, meta: &EnumMeta, path: &Path<'_>, ) -> Vec where S: ScalarValue, { let mut errors: Vec = vec![]; match value { // TODO: avoid this bad duplicate as_str() call. (value system refactor) InputValue::Scalar(scalar) if scalar.as_str().is_some() => { if let Some(name) = scalar.as_str() { if !meta.values.iter().any(|ev| ev.name == *name) { errors.push(unification_error( var_name, var_pos, path, format!(r#"Invalid value for enum "{}""#, meta.name), )) } } } InputValue::Enum(name) => { if !meta.values.iter().any(|ev| &ev.name == name) { errors.push(unification_error( var_name, var_pos, path, format!(r#"Invalid value for enum "{}""#, meta.name), )) } } _ => errors.push(unification_error( var_name, var_pos, path, format!(r#"Expected "{}", found not a string or enum"#, meta.name), )), } errors } fn unify_input_object( var_name: &str, var_pos: &SourcePosition, value: &InputValue, meta: &InputObjectMeta, schema: &SchemaType, path: &Path<'_>, ) -> Vec where S: ScalarValue, { let mut errors: Vec = vec![]; if let Some(ref obj) = value.to_object_value() { let mut keys = obj.keys().collect::>(); for input_field in &meta.input_fields { let mut has_value = false; keys.remove(&input_field.name.as_str()); if let Some(value) = obj.get(input_field.name.as_str()) { if !value.is_null() { has_value = true; errors.append(&mut unify_value( var_name, var_pos, value, &schema.make_type(&input_field.arg_type), schema, Path::ObjectField(&input_field.name, path), )); } } if !has_value && input_field.arg_type.is_non_null() { errors.push(unification_error( var_name, var_pos, &Path::ObjectField(&input_field.name, path), format!(r#"Expected "{}", found null"#, input_field.arg_type), )); } } for key in keys { errors.push(unification_error( var_name, var_pos, &Path::ObjectField(key, path), "Unknown field", )); } } else { errors.push(unification_error( var_name, var_pos, path, format!(r#"Expected "{}", found not an object"#, meta.name), )); } errors } fn is_absent_or_null(v: Option<&InputValue>) -> bool where S: ScalarValue, { v.map_or(true, InputValue::is_null) } fn unification_error( var_name: impl fmt::Display, var_pos: &SourcePosition, path: &Path<'_>, message: impl fmt::Display, ) -> RuleError { RuleError::new( &format!(r#"Variable "${var_name}" got invalid value. {path}{message}."#), &[*var_pos], ) } impl<'a> fmt::Display for Path<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Path::Root => write!(f, ""), Path::ArrayElement(idx, prev) => write!(f, "{prev}In element #{idx}: "), Path::ObjectField(name, prev) => write!(f, r#"{prev}In field "{name}": "#), } } } juniper-0.16.2/src/validation/mod.rs000064400000000000000000000012161046102023000154340ustar 00000000000000//! Query validation related methods and data structures mod context; mod input_value; mod multi_visitor; pub mod rules; mod traits; mod visitor; #[cfg(test)] pub(crate) mod test_harness; pub use self::{ context::{RuleError, ValidatorContext}, input_value::validate_input_values, multi_visitor::MultiVisitorNil, rules::visit_all_rules, traits::Visitor, visitor::visit, }; #[cfg(test)] pub use self::test_harness::{ expect_fails_fn, expect_fails_fn_with_schema, expect_fails_rule, expect_fails_rule_with_schema, expect_passes_fn, expect_passes_fn_with_schema, expect_passes_rule, expect_passes_rule_with_schema, }; juniper-0.16.2/src/validation/multi_visitor.rs000064400000000000000000000204721046102023000175730ustar 00000000000000use crate::{ ast::{ Directive, Document, Field, Fragment, FragmentSpread, InlineFragment, InputValue, Operation, Selection, VariableDefinition, }, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, Span, }; #[doc(hidden)] pub struct MultiVisitorNil; #[doc(hidden)] impl MultiVisitorNil { pub fn with(self, visitor: V) -> MultiVisitorCons { MultiVisitorCons(visitor, self) } } #[doc(hidden)] pub struct MultiVisitorCons(A, B); impl MultiVisitorCons { pub fn with(self, visitor: V) -> MultiVisitorCons { MultiVisitorCons(visitor, self) } } impl<'a, S> Visitor<'a, S> for MultiVisitorNil where S: ScalarValue {} impl<'a, A, B, S> Visitor<'a, S> for MultiVisitorCons where S: ScalarValue, A: Visitor<'a, S> + 'a, B: Visitor<'a, S> + 'a, { fn enter_document(&mut self, ctx: &mut ValidatorContext<'a, S>, doc: &'a Document) { self.0.enter_document(ctx, doc); self.1.enter_document(ctx, doc); } fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, doc: &'a Document) { self.0.exit_document(ctx, doc); self.1.exit_document(ctx, doc); } fn enter_operation_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { self.0.enter_operation_definition(ctx, op); self.1.enter_operation_definition(ctx, op); } fn exit_operation_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { self.0.exit_operation_definition(ctx, op); self.1.exit_operation_definition(ctx, op); } fn enter_fragment_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.0.enter_fragment_definition(ctx, f); self.1.enter_fragment_definition(ctx, f); } fn exit_fragment_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.0.exit_fragment_definition(ctx, f); self.1.exit_fragment_definition(ctx, f); } fn enter_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, def: &'a (Spanning<&'a str>, VariableDefinition), ) { self.0.enter_variable_definition(ctx, def); self.1.enter_variable_definition(ctx, def); } fn exit_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, def: &'a (Spanning<&'a str>, VariableDefinition), ) { self.0.exit_variable_definition(ctx, def); self.1.exit_variable_definition(ctx, def); } fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, d: &'a Spanning>, ) { self.0.enter_directive(ctx, d); self.1.enter_directive(ctx, d); } fn exit_directive(&mut self, ctx: &mut ValidatorContext<'a, S>, d: &'a Spanning>) { self.0.exit_directive(ctx, d); self.1.exit_directive(ctx, d); } fn enter_argument( &mut self, ctx: &mut ValidatorContext<'a, S>, arg: &'a (Spanning<&'a str>, Spanning>), ) { self.0.enter_argument(ctx, arg); self.1.enter_argument(ctx, arg); } fn exit_argument( &mut self, ctx: &mut ValidatorContext<'a, S>, arg: &'a (Spanning<&'a str>, Spanning>), ) { self.0.exit_argument(ctx, arg); self.1.exit_argument(ctx, arg); } fn enter_selection_set(&mut self, ctx: &mut ValidatorContext<'a, S>, s: &'a [Selection]) { self.0.enter_selection_set(ctx, s); self.1.enter_selection_set(ctx, s); } fn exit_selection_set(&mut self, ctx: &mut ValidatorContext<'a, S>, s: &'a [Selection]) { self.0.exit_selection_set(ctx, s); self.1.exit_selection_set(ctx, s); } fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>) { self.0.enter_field(ctx, f); self.1.enter_field(ctx, f); } fn exit_field(&mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>) { self.0.exit_field(ctx, f); self.1.exit_field(ctx, f); } fn enter_fragment_spread( &mut self, ctx: &mut ValidatorContext<'a, S>, s: &'a Spanning>, ) { self.0.enter_fragment_spread(ctx, s); self.1.enter_fragment_spread(ctx, s); } fn exit_fragment_spread( &mut self, ctx: &mut ValidatorContext<'a, S>, s: &'a Spanning>, ) { self.0.exit_fragment_spread(ctx, s); self.1.exit_fragment_spread(ctx, s); } fn enter_inline_fragment( &mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.0.enter_inline_fragment(ctx, f); self.1.enter_inline_fragment(ctx, f); } fn exit_inline_fragment( &mut self, ctx: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.0.exit_inline_fragment(ctx, f); self.1.exit_inline_fragment(ctx, f); } fn enter_null_value(&mut self, ctx: &mut ValidatorContext<'a, S>, n: SpannedInput<'a, ()>) { self.0.enter_null_value(ctx, n); self.1.enter_null_value(ctx, n); } fn exit_null_value(&mut self, ctx: &mut ValidatorContext<'a, S>, n: SpannedInput<'a, ()>) { self.0.exit_null_value(ctx, n); self.1.exit_null_value(ctx, n); } fn enter_scalar_value(&mut self, ctx: &mut ValidatorContext<'a, S>, n: SpannedInput<'a, S>) { self.0.enter_scalar_value(ctx, n); self.1.enter_scalar_value(ctx, n); } fn exit_scalar_value(&mut self, ctx: &mut ValidatorContext<'a, S>, n: SpannedInput<'a, S>) { self.0.exit_scalar_value(ctx, n); self.1.exit_scalar_value(ctx, n); } fn enter_enum_value(&mut self, ctx: &mut ValidatorContext<'a, S>, s: SpannedInput<'a, String>) { self.0.enter_enum_value(ctx, s); self.1.enter_enum_value(ctx, s); } fn exit_enum_value(&mut self, ctx: &mut ValidatorContext<'a, S>, s: SpannedInput<'a, String>) { self.0.exit_enum_value(ctx, s); self.1.exit_enum_value(ctx, s); } fn enter_variable_value( &mut self, ctx: &mut ValidatorContext<'a, S>, s: SpannedInput<'a, String>, ) { self.0.enter_variable_value(ctx, s); self.1.enter_variable_value(ctx, s); } fn exit_variable_value( &mut self, ctx: &mut ValidatorContext<'a, S>, s: SpannedInput<'a, String>, ) { self.0.exit_variable_value(ctx, s); self.1.exit_variable_value(ctx, s); } fn enter_list_value( &mut self, ctx: &mut ValidatorContext<'a, S>, l: SpannedInput<'a, Vec>>>, ) { self.0.enter_list_value(ctx, l); self.1.enter_list_value(ctx, l); } fn exit_list_value( &mut self, ctx: &mut ValidatorContext<'a, S>, l: SpannedInput<'a, Vec>>>, ) { self.0.exit_list_value(ctx, l); self.1.exit_list_value(ctx, l); } fn enter_object_value(&mut self, ctx: &mut ValidatorContext<'a, S>, o: SpannedObject<'a, S>) { self.0.enter_object_value(ctx, o); self.1.enter_object_value(ctx, o); } fn exit_object_value(&mut self, ctx: &mut ValidatorContext<'a, S>, o: SpannedObject<'a, S>) { self.0.exit_object_value(ctx, o); self.1.exit_object_value(ctx, o); } fn enter_object_field( &mut self, ctx: &mut ValidatorContext<'a, S>, f: (SpannedInput<'a, String>, SpannedInput<'a, InputValue>), ) { self.0.enter_object_field(ctx, f); self.1.enter_object_field(ctx, f); } fn exit_object_field( &mut self, ctx: &mut ValidatorContext<'a, S>, f: (SpannedInput<'a, String>, SpannedInput<'a, InputValue>), ) { self.0.exit_object_field(ctx, f); self.1.exit_object_field(ctx, f); } } type SpannedInput<'a, T> = Spanning<&'a T, &'a Span>; type SpannedObject<'a, S> = SpannedInput<'a, Vec<(Spanning, Spanning>)>>; juniper-0.16.2/src/validation/rules/arguments_of_correct_type.rs000064400000000000000000000704441046102023000232730ustar 00000000000000use std::fmt; use crate::{ ast::{Directive, Field, InputValue}, parser::Spanning, schema::meta::Argument, types::utilities::validate_literal_value, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct ArgumentsOfCorrectType<'a, S: fmt::Debug + 'a> { current_args: Option<&'a Vec>>, } pub fn factory<'a, S: fmt::Debug>() -> ArgumentsOfCorrectType<'a, S> { ArgumentsOfCorrectType { current_args: None } } impl<'a, S> Visitor<'a, S> for ArgumentsOfCorrectType<'a, S> where S: ScalarValue, { fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, directive: &'a Spanning>, ) { self.current_args = ctx .schema .directive_by_name(directive.item.name.item) .map(|d| &d.arguments); } fn exit_directive(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.current_args = None; } fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>) { self.current_args = ctx .parent_type() .and_then(|t| t.field_by_name(field.item.name.item)) .and_then(|f| f.arguments.as_ref()); } fn exit_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.current_args = None; } fn enter_argument( &mut self, ctx: &mut ValidatorContext<'a, S>, (arg_name, arg_value): &'a (Spanning<&'a str>, Spanning>), ) { if let Some(argument_meta) = self .current_args .and_then(|args| args.iter().find(|a| a.name == arg_name.item)) { let meta_type = ctx.schema.make_type(&argument_meta.arg_type); if let Some(err) = validate_literal_value(ctx.schema, &meta_type, &arg_value.item) { ctx.report_error(&error_message(arg_name.item, err), &[arg_value.span.start]); } } } } fn error_message(arg_name: impl fmt::Display, msg: impl fmt::Display) -> String { format!("Invalid value for argument \"{arg_name}\", reason: {msg}") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, types::utilities::error, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn null_into_nullable_int() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: null) } } "#, ); } #[test] fn null_into_nullable_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: null) } } "#, ); } #[test] fn null_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { nonNullIntArgField(nonNullIntArg: null) } } "#, &[RuleError::new( &error_message("nonNullIntArg", error::non_null("Int!")), &[SourcePosition::new(97, 3, 50)], )], ); } #[test] fn null_into_list() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { nonNullStringListArgField(nonNullStringListArg: null) } } "#, &[RuleError::new( &error_message("nonNullStringListArg", error::non_null("[String!]!")), &[SourcePosition::new(111, 3, 64)], )], ); } #[test] fn good_int_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: 2) } } "#, ); } #[test] fn good_boolean_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { booleanArgField(booleanArg: true) } } "#, ); } #[test] fn good_string_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringArgField(stringArg: "foo") } } "#, ); } #[test] fn good_float_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { floatArgField(floatArg: 1.1) } } "#, ); } #[test] fn int_into_float() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { floatArgField(floatArg: 1) } } "#, ); } #[test] fn int_into_id() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { idArgField(idArg: 1) } } "#, ); } #[test] fn string_into_id() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { idArgField(idArg: "someIdString") } } "#, ); } #[test] fn good_enum_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: SIT) } } "#, ); } #[test] fn int_into_string() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringArgField(stringArg: 1) } } "#, &[RuleError::new( &error_message("stringArg", error::type_value("1", "String")), &[SourcePosition::new(89, 3, 42)], )], ); } #[test] fn float_into_string() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringArgField(stringArg: 1.0) } } "#, &[RuleError::new( &error_message("stringArg", error::type_value("1", "String")), &[SourcePosition::new(89, 3, 42)], )], ); } #[test] fn boolean_into_string() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringArgField(stringArg: true) } } "#, &[RuleError::new( &error_message("stringArg", error::type_value("true", "String")), &[SourcePosition::new(89, 3, 42)], )], ); } #[test] fn unquoted_string_into_string() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringArgField(stringArg: BAR) } } "#, &[RuleError::new( &error_message("stringArg", error::type_value("BAR", "String")), &[SourcePosition::new(89, 3, 42)], )], ); } #[test] fn string_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: "3") } } "#, &[RuleError::new( &error_message("intArg", error::type_value("\"3\"", "Int")), &[SourcePosition::new(83, 3, 36)], )], ); } #[test] fn unquoted_string_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: FOO) } } "#, &[RuleError::new( &error_message("intArg", error::type_value("FOO", "Int")), &[SourcePosition::new(83, 3, 36)], )], ); } #[test] fn simple_float_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: 3.0) } } "#, &[RuleError::new( &error_message("intArg", error::type_value("3", "Int")), &[SourcePosition::new(83, 3, 36)], )], ); } #[test] fn float_into_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { intArgField(intArg: 3.333) } } "#, &[RuleError::new( &error_message("intArg", error::type_value("3.333", "Int")), &[SourcePosition::new(83, 3, 36)], )], ); } #[test] fn string_into_float() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { floatArgField(floatArg: "3.333") } } "#, &[RuleError::new( &error_message("floatArg", error::type_value("\"3.333\"", "Float")), &[SourcePosition::new(87, 3, 40)], )], ); } #[test] fn boolean_into_float() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { floatArgField(floatArg: true) } } "#, &[RuleError::new( &error_message("floatArg", error::type_value("true", "Float")), &[SourcePosition::new(87, 3, 40)], )], ); } #[test] fn unquoted_into_float() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { floatArgField(floatArg: FOO) } } "#, &[RuleError::new( &error_message("floatArg", error::type_value("FOO", "Float")), &[SourcePosition::new(87, 3, 40)], )], ); } #[test] fn int_into_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { booleanArgField(booleanArg: 2) } } "#, &[RuleError::new( &error_message("booleanArg", error::type_value("2", "Boolean")), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn float_into_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { booleanArgField(booleanArg: 1.0) } } "#, &[RuleError::new( &error_message("booleanArg", error::type_value("1", "Boolean")), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn string_into_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { booleanArgField(booleanArg: "true") } } "#, &[RuleError::new( &error_message("booleanArg", error::type_value("\"true\"", "Boolean")), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn unquoted_into_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { booleanArgField(booleanArg: TRUE) } } "#, &[RuleError::new( &error_message("booleanArg", error::type_value("TRUE", "Boolean")), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn float_into_id() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { idArgField(idArg: 1.0) } } "#, &[RuleError::new( &error_message("idArg", error::type_value("1", "ID")), &[SourcePosition::new(81, 3, 34)], )], ); } #[test] fn boolean_into_id() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { idArgField(idArg: true) } } "#, &[RuleError::new( &error_message("idArg", error::type_value("true", "ID")), &[SourcePosition::new(81, 3, 34)], )], ); } #[test] fn unquoted_into_id() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { idArgField(idArg: SOMETHING) } } "#, &[RuleError::new( &error_message("idArg", error::type_value("SOMETHING", "ID")), &[SourcePosition::new(81, 3, 34)], )], ); } #[test] fn int_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: 2) } } "#, &[RuleError::new( &error_message("dogCommand", error::enum_value("2", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn float_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: 1.0) } } "#, &[RuleError::new( &error_message("dogCommand", error::enum_value("1", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn string_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: "SIT") } } "#, &[RuleError::new( &error_message("dogCommand", error::enum_value("\"SIT\"", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn boolean_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: true) } } "#, &[RuleError::new( &error_message("dogCommand", error::enum_value("true", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn unknown_enum_value_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: JUGGLE) } } "#, &[RuleError::new( &error_message("dogCommand", error::type_value("JUGGLE", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn different_case_enum_value_into_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: sit) } } "#, &[RuleError::new( &error_message("dogCommand", error::type_value("sit", "DogCommand")), &[SourcePosition::new(79, 3, 44)], )], ); } #[test] fn good_list_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: ["one", "two"]) } } "#, ); } #[test] fn empty_list_value() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: []) } } "#, ); } #[test] fn single_value_into_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: "one") } } "#, ); } #[test] fn incorrect_item_type() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: ["one", 2]) } } "#, &[RuleError::new( &error_message("stringListArg", error::type_value("2", "String")), &[SourcePosition::new(97, 3, 50)], )], ); } #[test] fn single_value_of_incorrect_type() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { stringListArgField(stringListArg: 1) } } "#, &[RuleError::new( &error_message("stringListArg", error::type_value("1", "String")), &[SourcePosition::new(97, 3, 50)], )], ); } #[test] fn arg_on_optional_arg() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { isHousetrained(atOtherHomes: true) } } "#, ); } #[test] fn no_arg_on_optional_arg() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { isHousetrained } } "#, ); } #[test] fn multiple_args() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req1: 1, req2: 2) } } "#, ); } #[test] fn multiple_args_reverse_order() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req2: 2, req1: 1) } } "#, ); } #[test] fn no_args_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts } } "#, ); } #[test] fn one_arg_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts(opt1: 1) } } "#, ); } #[test] fn second_arg_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts(opt2: 1) } } "#, ); } #[test] fn multiple_reqs_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4) } } "#, ); } #[test] fn multiple_reqs_and_one_opt_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5) } } "#, ); } #[test] fn all_reqs_and_opts_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) } } "#, ); } #[test] fn incorrect_value_type() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req2: "two", req1: "one") } } "#, &[ RuleError::new( &error_message("req2", error::type_value("\"two\"", "Int")), &[SourcePosition::new(82, 3, 35)], ), RuleError::new( &error_message("req1", error::type_value("\"one\"", "Int")), &[SourcePosition::new(95, 3, 48)], ), ], ); } #[test] fn incorrect_value_and_missing_argument() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req1: "one") } } "#, &[RuleError::new( &error_message("req1", error::type_value("\"one\"", "Int")), &[SourcePosition::new(82, 3, 35)], )], ); } #[test] fn optional_arg_despite_required_field_in_type() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField } } "#, ); } #[test] fn partial_object_only_required() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { requiredField: true }) } } "#, ); } #[test] fn partial_object_required_field_can_be_falsy() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { requiredField: false }) } } "#, ); } #[test] fn partial_object_including_required() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { requiredField: true, intField: 4 }) } } "#, ); } #[test] fn full_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { requiredField: true, intField: 4, stringField: "foo", booleanField: false, stringListField: ["one", "two"] }) } } "#, ); } #[test] fn full_object_with_fields_in_different_order() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { stringListField: ["one", "two"], booleanField: false, requiredField: true, stringField: "foo", intField: 4, }) } } "#, ); } #[test] fn partial_object_missing_required() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { intField: 4 }) } } "#, &[RuleError::new( &error_message( "complexArg", error::missing_fields("ComplexInput", "\"requiredField\""), ), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn partial_object_invalid_field_type() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { stringListField: ["one", 2], requiredField: true, }) } } "#, &[RuleError::new( &error_message( "complexArg", error::field( "ComplexInput", "stringListField", error::type_value("2", "String"), ), ), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn partial_object_unknown_field_arg() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { complexArgField(complexArg: { requiredField: true, unknownField: "value" }) } } "#, &[RuleError::new( &error_message( "complexArg", error::unknown_field("ComplexInput", "unknownField"), ), &[SourcePosition::new(91, 3, 44)], )], ); } #[test] fn directive_with_valid_types() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @include(if: true) { name } human @skip(if: false) { name } } "#, ); } #[test] fn directive_with_incorrect_types() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @include(if: "yes") { name @skip(if: ENUM) } } "#, &[ RuleError::new( &error_message("if", error::type_value("\"yes\"", "Boolean")), &[SourcePosition::new(46, 2, 31)], ), RuleError::new( &error_message("if", error::type_value("ENUM", "Boolean")), &[SourcePosition::new(86, 3, 31)], ), ], ); } } juniper-0.16.2/src/validation/rules/default_values_of_correct_type.rs000064400000000000000000000137261046102023000242710ustar 00000000000000use std::fmt; use crate::{ ast::VariableDefinition, parser::Spanning, types::utilities::validate_literal_value, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct DefaultValuesOfCorrectType; pub fn factory() -> DefaultValuesOfCorrectType { DefaultValuesOfCorrectType } impl<'a, S> Visitor<'a, S> for DefaultValuesOfCorrectType where S: ScalarValue, { fn enter_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, (var_name, var_def): &'a (Spanning<&'a str>, VariableDefinition), ) { if let Some(Spanning { item: ref var_value, ref span, }) = var_def.default_value { if var_def.var_type.item.is_non_null() { ctx.report_error( &non_null_error_message(var_name.item, &var_def.var_type.item), &[span.start], ) } else { let meta_type = ctx.schema.make_type(&var_def.var_type.item); if let Some(err) = validate_literal_value(ctx.schema, &meta_type, var_value) { ctx.report_error( &type_error_message(var_name.item, &var_def.var_type.item, err), &[span.start], ); } } } } } fn type_error_message( arg_name: impl fmt::Display, type_name: impl fmt::Display, reason: impl fmt::Display, ) -> String { format!( "Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\", \ reason: {reason}", ) } fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( "Argument \"{arg_name}\" has type \"{type_name}\" and is not nullable, \ so it can't have a default value", ) } #[cfg(test)] mod tests { use super::{factory, non_null_error_message, type_error_message}; use crate::{ parser::SourcePosition, types::utilities::error, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn variables_with_no_default_values() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query NullableValues($a: Int, $b: String, $c: ComplexInput) { dog { name } } "#, ); } #[test] fn required_variables_without_default_values() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query RequiredValues($a: Int!, $b: String!) { dog { name } } "#, ); } #[test] fn variables_with_valid_default_values() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query WithDefaultValues( $a: Int = 1, $b: String = "ok", $c: ComplexInput = { requiredField: true, intField: 3 } ) { dog { name } } "#, ); } #[test] fn no_required_variables_with_default_values() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { dog { name } } "#, &[ RuleError::new( &non_null_error_message("a", "Int!"), &[SourcePosition::new(55, 1, 54)], ), RuleError::new( &non_null_error_message("b", "String!"), &[SourcePosition::new(72, 1, 71)], ), ], ); } #[test] fn variables_with_invalid_default_values() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query InvalidDefaultValues( $a: Int = "one", $b: String = 4, $c: ComplexInput = "notverycomplex" ) { dog { name } } "#, &[ RuleError::new( &type_error_message("a", "Int", error::type_value("\"one\"", "Int")), &[SourcePosition::new(67, 2, 26)], ), RuleError::new( &type_error_message("b", "String", error::type_value("4", "String")), &[SourcePosition::new(103, 3, 29)], ), RuleError::new( &type_error_message( "c", "ComplexInput", error::type_value("\"notverycomplex\"", "ComplexInput"), ), &[SourcePosition::new(141, 4, 35)], ), ], ); } #[test] fn complex_variables_missing_required_field() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query MissingRequiredField($a: ComplexInput = {intField: 3}) { dog { name } } "#, &[RuleError::new( &type_error_message( "a", "ComplexInput", error::missing_fields("ComplexInput", "\"requiredField\""), ), &[SourcePosition::new(59, 1, 58)], )], ); } #[test] fn list_variables_with_invalid_item() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query InvalidItem($a: [String] = ["one", 2]) { dog { name } } "#, &[RuleError::new( &type_error_message("a", "[String]", error::type_value("2", "String")), &[SourcePosition::new(46, 1, 45)], )], ); } } juniper-0.16.2/src/validation/rules/disable_introspection.rs000064400000000000000000000116251046102023000223770ustar 00000000000000//! Validation rule checking whether a GraphQL operation contains introspection (`__schema` or //! `__type` fields). use crate::{ ast::Field, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; /// Validation rule checking whether a GraphQL operation contains introspection (`__schema` or /// `__type` fields). pub struct DisableIntrospection; /// Produces a new [`DisableIntrospection`] validation rule. #[inline] #[must_use] pub fn factory() -> DisableIntrospection { DisableIntrospection } impl<'a, S> Visitor<'a, S> for DisableIntrospection where S: ScalarValue, { fn enter_field( &mut self, context: &mut ValidatorContext<'a, S>, field: &'a Spanning>, ) { let field_name = field.item.name.item; if matches!(field_name, "__schema" | "__type") { context.report_error(&error_message(field_name), &[field.item.name.span.start]); } } } fn error_message(field_name: &str) -> String { format!("GraphQL introspection is not allowed, but the operation contained `{field_name}`") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn allows_regular_fields() { // language=GraphQL expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query { user { name ... on User { email } alias: email ... { typeless } friends { name } } } "#, ); } #[test] fn allows_typename_field() { // language=GraphQL expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query { __typename user { __typename ... on User { __typename } ... { __typename } friends { __typename } } } "#, ); } #[test] fn forbids_query_schema() { // language=GraphQL expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query { __schema { queryType { name } } } "#, &[RuleError::new( &error_message("__schema"), &[SourcePosition::new(37, 2, 16)], )], ); } #[test] fn forbids_query_type() { // language=GraphQL expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query { __type( name: "Query" ) { name } } "#, &[RuleError::new( &error_message("__type"), &[SourcePosition::new(37, 2, 16)], )], ); } #[test] fn forbids_field_type() { // language=GraphQL expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query { user { name ... on User { email } alias: email ... { typeless } friends { name } __type } } "#, &[RuleError::new( &error_message("__type"), &[SourcePosition::new(370, 14, 20)], )], ); } #[test] fn forbids_field_schema() { // language=GraphQL expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query { user { name ... on User { email } alias: email ... { typeless } friends { name } __schema } } "#, &[RuleError::new( &error_message("__schema"), &[SourcePosition::new(370, 14, 20)], )], ); } } juniper-0.16.2/src/validation/rules/fields_on_correct_type.rs000064400000000000000000000245731046102023000225460ustar 00000000000000use crate::{ ast::Field, parser::Spanning, schema::meta::MetaType, validation::{ValidatorContext, Visitor}, value::ScalarValue, Operation, OperationType, Selection, }; pub struct FieldsOnCorrectType; pub fn factory() -> FieldsOnCorrectType { FieldsOnCorrectType } impl<'a, S> Visitor<'a, S> for FieldsOnCorrectType where S: ScalarValue, { fn enter_operation_definition( &mut self, context: &mut ValidatorContext<'a, S>, operation: &'a Spanning>, ) { // https://spec.graphql.org/October2021/#note-bc213 if let OperationType::Subscription = operation.item.operation_type { for selection in &operation.item.selection_set { if let Selection::Field(field) = selection { if field.item.name.item == "__typename" { context.report_error( "`__typename` may not be included as a root \ field in a subscription operation", &[field.item.name.span.start], ); } } } } } fn enter_field( &mut self, context: &mut ValidatorContext<'a, S>, field: &'a Spanning>, ) { { if let Some(parent_type) = context.parent_type() { let field_name = &field.item.name; let type_name = parent_type.name().unwrap_or(""); if parent_type.field_by_name(field_name.item).is_none() { if let MetaType::Union(..) = *parent_type { // You can query for `__typename` on a union, // but it isn't a field on the union...it is // instead on the resulting object returned. if field_name.item == "__typename" { return; } } context.report_error( &error_message(field_name.item, type_name), &[field_name.span.start], ); } } } } } fn error_message(field: &str, type_name: &str) -> String { format!(r#"Unknown field "{field}" on type "{type_name}""#) } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn selection_on_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectFieldSelection on Dog { __typename name } "#, ); } #[test] fn aliased_selection_on_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment aliasedObjectFieldSelection on Dog { tn : __typename otherName : name } "#, ); } #[test] fn selection_on_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceFieldSelection on Pet { __typename name } "#, ); } #[test] fn aliased_selection_on_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceFieldSelection on Pet { otherName : name } "#, ); } #[test] fn lying_alias_selection() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment lyingAliasSelection on Dog { name : nickname } "#, ); } #[test] fn ignores_unknown_type() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment unknownSelection on UnknownType { unknownField } "#, ); } #[test] fn nested_unknown_fields() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment typeKnownAgain on Pet { unknown_pet_field { ... on Cat { unknown_cat_field } } } "#, &[ RuleError::new( &error_message("unknown_pet_field", "Pet"), &[SourcePosition::new(56, 2, 12)], ), RuleError::new( &error_message("unknown_cat_field", "Cat"), &[SourcePosition::new(119, 4, 16)], ), ], ); } #[test] fn unknown_field_on_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fieldNotDefined on Dog { meowVolume } "#, &[RuleError::new( &error_message("meowVolume", "Dog"), &[SourcePosition::new(57, 2, 12)], )], ); } #[test] fn ignores_deeply_unknown_field() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment deepFieldNotDefined on Dog { unknown_field { deeper_unknown_field } } "#, &[RuleError::new( &error_message("unknown_field", "Dog"), &[SourcePosition::new(61, 2, 12)], )], ); } #[test] fn unknown_subfield() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment subFieldNotDefined on Human { pets { unknown_field } } "#, &[RuleError::new( &error_message("unknown_field", "Pet"), &[SourcePosition::new(83, 3, 14)], )], ); } #[test] fn unknown_field_on_inline_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fieldNotDefined on Pet { ... on Dog { meowVolume } } "#, &[RuleError::new( &error_message("meowVolume", "Dog"), &[SourcePosition::new(84, 3, 14)], )], ); } #[test] fn unknown_aliased_target() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment aliasedFieldTargetNotDefined on Dog { volume : mooVolume } "#, &[RuleError::new( &error_message("mooVolume", "Dog"), &[SourcePosition::new(79, 2, 21)], )], ); } #[test] fn unknown_aliased_lying_field_target() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment aliasedLyingFieldTargetNotDefined on Dog { barkVolume : kawVolume } "#, &[RuleError::new( &error_message("kawVolume", "Dog"), &[SourcePosition::new(88, 2, 25)], )], ); } #[test] fn not_defined_on_interface() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment notDefinedOnInterface on Pet { tailLength } "#, &[RuleError::new( &error_message("tailLength", "Pet"), &[SourcePosition::new(63, 2, 12)], )], ); } #[test] fn defined_in_concrete_types_but_not_interface() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment definedOnImplementorsButNotInterface on Pet { nickname } "#, &[RuleError::new( &error_message("nickname", "Pet"), &[SourcePosition::new(78, 2, 12)], )], ); } #[test] fn meta_field_on_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment definedOnImplementorsButNotInterface on Pet { __typename } "#, ); } #[test] fn fields_on_union() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment definedOnImplementorsQueriedOnUnion on CatOrDog { name } "#, &[RuleError::new( &error_message("name", "CatOrDog"), &[SourcePosition::new(82, 2, 12)], )], ); } #[test] fn typename_on_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectFieldSelection on Pet { __typename ... on Dog { name } ... on Cat { name } } "#, ); } #[test] fn valid_field_in_inline_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectFieldSelection on Pet { ... on Dog { name } ... { name } } "#, ); } #[test] fn forbids_typename_on_subscription() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#"subscription { __typename }"#, &[RuleError::new( "`__typename` may not be included as a root field in a \ subscription operation", &[SourcePosition::new(15, 0, 15)], )], ); } #[test] fn forbids_typename_on_explicit_subscription() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#"subscription SubscriptionRoot { __typename }"#, &[RuleError::new( "`__typename` may not be included as a root field in a \ subscription operation", &[SourcePosition::new(32, 0, 32)], )], ); } } juniper-0.16.2/src/validation/rules/fragments_on_composite_types.rs000064400000000000000000000117701046102023000240050ustar 00000000000000use crate::{ ast::{Fragment, InlineFragment}, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct FragmentsOnCompositeTypes; pub fn factory() -> FragmentsOnCompositeTypes { FragmentsOnCompositeTypes } impl<'a, S> Visitor<'a, S> for FragmentsOnCompositeTypes where S: ScalarValue, { fn enter_fragment_definition( &mut self, context: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { { if let Some(current_type) = context.current_type() { if !current_type.is_composite() { let type_name = current_type.name().unwrap_or(""); let type_cond = &f.item.type_condition; context.report_error( &error_message(Some(f.item.name.item), type_name), &[type_cond.span.start], ); } } } } fn enter_inline_fragment( &mut self, context: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { { if let Some(ref type_cond) = f.item.type_condition { let invalid_type_name = context .current_type() .iter() .filter(|&t| !t.is_composite()) .map(|t| t.name().unwrap_or("")) .next(); if let Some(name) = invalid_type_name { context.report_error(&error_message(None, name), &[type_cond.span.start]); } } } } } fn error_message(fragment_name: Option<&str>, on_type: &str) -> String { if let Some(name) = fragment_name { format!(r#"Fragment "{name}" cannot condition non composite type "{on_type}"#) } else { format!(r#"Fragment cannot condition on non composite type "{on_type}""#) } } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn on_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment validFragment on Dog { barks } "#, ); } #[test] fn on_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment validFragment on Pet { name } "#, ); } #[test] fn on_object_inline() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment validFragment on Pet { ... on Dog { barks } } "#, ); } #[test] fn on_inline_without_type_cond() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment validFragment on Pet { ... { name } } "#, ); } #[test] fn on_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment validFragment on CatOrDog { __typename } "#, ); } #[test] fn not_on_scalar() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarFragment on Boolean { bad } "#, &[RuleError::new( &error_message(Some("scalarFragment"), "Boolean"), &[SourcePosition::new(38, 1, 37)], )], ); } #[test] fn not_on_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarFragment on FurColor { bad } "#, &[RuleError::new( &error_message(Some("scalarFragment"), "FurColor"), &[SourcePosition::new(38, 1, 37)], )], ); } #[test] fn not_on_input_object() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment inputFragment on ComplexInput { stringField } "#, &[RuleError::new( &error_message(Some("inputFragment"), "ComplexInput"), &[SourcePosition::new(37, 1, 36)], )], ); } #[test] fn not_on_scalar_inline() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidFragment on Pet { ... on String { barks } } "#, &[RuleError::new( &error_message(None, "String"), &[SourcePosition::new(64, 2, 19)], )], ); } } juniper-0.16.2/src/validation/rules/known_argument_names.rs000064400000000000000000000172771046102023000222460ustar 00000000000000use crate::{ ast::{Directive, Field, InputValue}, parser::Spanning, schema::meta::Argument, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; use std::fmt::Debug; #[derive(Debug)] enum ArgumentPosition<'a> { Directive(&'a str), Field(&'a str, &'a str), } pub struct KnownArgumentNames<'a, S: Debug + 'a> { current_args: Option<(ArgumentPosition<'a>, &'a Vec>)>, } pub fn factory<'a, S: Debug>() -> KnownArgumentNames<'a, S> { KnownArgumentNames { current_args: None } } impl<'a, S> Visitor<'a, S> for KnownArgumentNames<'a, S> where S: ScalarValue, { fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, directive: &'a Spanning>, ) { self.current_args = ctx .schema .directive_by_name(directive.item.name.item) .map(|d| { ( ArgumentPosition::Directive(directive.item.name.item), &d.arguments, ) }); } fn exit_directive(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.current_args = None; } fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>) { self.current_args = ctx .parent_type() .and_then(|t| t.field_by_name(field.item.name.item)) .and_then(|f| f.arguments.as_ref()) .map(|args| { ( ArgumentPosition::Field( field.item.name.item, ctx.parent_type() .expect("Parent type should exist") .name() .expect("Parent type should be named"), ), args, ) }); } fn exit_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.current_args = None; } fn enter_argument( &mut self, ctx: &mut ValidatorContext<'a, S>, (arg_name, _): &'a (Spanning<&'a str>, Spanning>), ) { if let Some((ref pos, args)) = self.current_args { if !args.iter().any(|a| a.name == arg_name.item) { let message = match *pos { ArgumentPosition::Field(field_name, type_name) => { field_error_message(arg_name.item, field_name, type_name) } ArgumentPosition::Directive(directive_name) => { directive_error_message(arg_name.item, directive_name) } }; ctx.report_error(&message, &[arg_name.span.start]); } } } } fn field_error_message(arg_name: &str, field_name: &str, type_name: &str) -> String { format!(r#"Unknown argument "{arg_name}" on field "{field_name}" of type "{type_name}""#) } fn directive_error_message(arg_name: &str, directive_name: &str) -> String { format!(r#"Unknown argument "{arg_name}" on directive "{directive_name}""#) } #[cfg(test)] mod tests { use super::{directive_error_message, factory, field_error_message}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn single_arg_is_known() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment argOnRequiredArg on Dog { doesKnowCommand(dogCommand: SIT) } "#, ); } #[test] fn multiple_args_are_known() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment multipleArgs on ComplicatedArgs { multipleReqs(req1: 1, req2: 2) } "#, ); } #[test] fn ignores_args_of_unknown_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment argOnUnknownField on Dog { unknownField(unknownArg: SIT) } "#, ); } #[test] fn multiple_args_in_reverse_order_are_known() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment multipleArgsReverseOrder on ComplicatedArgs { multipleReqs(req2: 2, req1: 1) } "#, ); } #[test] fn no_args_on_optional_arg() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment noArgOnOptionalArg on Dog { isHousetrained } "#, ); } #[test] fn args_are_known_deeply() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(dogCommand: SIT) } human { pet { ... on Dog { doesKnowCommand(dogCommand: SIT) } } } } "#, ); } #[test] fn directive_args_are_known() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @skip(if: true) } "#, ); } #[test] fn undirective_args_are_invalid() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @skip(unless: true) } "#, &[RuleError::new( &directive_error_message("unless", "skip"), &[SourcePosition::new(35, 2, 22)], )], ); } #[test] fn invalid_arg_name() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidArgName on Dog { doesKnowCommand(unknown: true) } "#, &[RuleError::new( &field_error_message("unknown", "doesKnowCommand", "Dog"), &[SourcePosition::new(72, 2, 28)], )], ); } #[test] fn unknown_args_amongst_known_args() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment oneGoodArgOneInvalidArg on Dog { doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true) } "#, &[ RuleError::new( &field_error_message("whoknows", "doesKnowCommand", "Dog"), &[SourcePosition::new(81, 2, 28)], ), RuleError::new( &field_error_message("unknown", "doesKnowCommand", "Dog"), &[SourcePosition::new(111, 2, 58)], ), ], ); } #[test] fn unknown_args_deeply() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { doesKnowCommand(unknown: true) } human { pet { ... on Dog { doesKnowCommand(unknown: true) } } } } "#, &[ RuleError::new( &field_error_message("unknown", "doesKnowCommand", "Dog"), &[SourcePosition::new(61, 3, 30)], ), RuleError::new( &field_error_message("unknown", "doesKnowCommand", "Dog"), &[SourcePosition::new(193, 8, 34)], ), ], ); } } juniper-0.16.2/src/validation/rules/known_directives.rs000064400000000000000000000230611046102023000213660ustar 00000000000000use crate::{ ast::{ Directive, Field, Fragment, FragmentSpread, InlineFragment, Operation, OperationType, VariableDefinition, }, parser::Spanning, schema::model::DirectiveLocation, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct KnownDirectives { location_stack: Vec, } pub fn factory() -> KnownDirectives { KnownDirectives { location_stack: Vec::new(), } } impl<'a, S> Visitor<'a, S> for KnownDirectives where S: ScalarValue, { fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { self.location_stack.push(match op.item.operation_type { OperationType::Query => DirectiveLocation::Query, OperationType::Mutation => DirectiveLocation::Mutation, OperationType::Subscription => DirectiveLocation::Subscription, }); } fn exit_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { let top = self.location_stack.pop(); assert!( top == Some(DirectiveLocation::Query) || top == Some(DirectiveLocation::Mutation) || top == Some(DirectiveLocation::Subscription) ); } fn enter_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.location_stack.push(DirectiveLocation::Field); } fn exit_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { let top = self.location_stack.pop(); assert_eq!(top, Some(DirectiveLocation::Field)); } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { self.location_stack .push(DirectiveLocation::FragmentDefinition); } fn exit_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { let top = self.location_stack.pop(); assert_eq!(top, Some(DirectiveLocation::FragmentDefinition)); } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { self.location_stack.push(DirectiveLocation::FragmentSpread); } fn exit_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { let top = self.location_stack.pop(); assert_eq!(top, Some(DirectiveLocation::FragmentSpread)); } fn enter_inline_fragment( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { self.location_stack.push(DirectiveLocation::InlineFragment); } fn exit_inline_fragment( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { let top = self.location_stack.pop(); assert_eq!(top, Some(DirectiveLocation::InlineFragment)); } fn enter_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, VariableDefinition), ) { self.location_stack .push(DirectiveLocation::VariableDefinition); } fn exit_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, VariableDefinition), ) { let top = self.location_stack.pop(); assert_eq!(top, Some(DirectiveLocation::VariableDefinition)); } fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, directive: &'a Spanning>, ) { let directive_name = &directive.item.name.item; if let Some(directive_type) = ctx.schema.directive_by_name(directive_name) { if let Some(current_location) = self.location_stack.last() { if !directive_type .locations .iter() .any(|l| l == current_location) { ctx.report_error( &misplaced_error_message(directive_name, current_location), &[directive.span.start], ); } } } else { ctx.report_error( &unknown_error_message(directive_name), &[directive.span.start], ); } } } fn unknown_error_message(directive_name: &str) -> String { format!(r#"Unknown directive "{directive_name}""#) } fn misplaced_error_message(directive_name: &str, location: &DirectiveLocation) -> String { format!(r#"Directive "{directive_name}" may not be used on {location}"#) } #[cfg(test)] mod tests { use super::{factory, misplaced_error_message, unknown_error_message}; use crate::{ parser::SourcePosition, schema::model::DirectiveLocation, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn with_no_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { name ...Frag } fragment Frag on Dog { name } "#, ); } #[test] fn with_known_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @include(if: true) { name } human @skip(if: false) { name } } "#, ); } #[test] fn with_unknown_directive() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @unknown(directive: "value") { name } } "#, &[RuleError::new( &unknown_error_message("unknown"), &[SourcePosition::new(29, 2, 16)], )], ); } #[test] fn with_unknown_directive_on_var_definition() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#"query Foo( $var1: Int = 1 @skip(if: true) @unknown, $var2: String @deprecated ) { name }"#, &[ RuleError::new( &misplaced_error_message("skip", &DirectiveLocation::VariableDefinition), &[SourcePosition::new(42, 1, 31)], ), RuleError::new( &unknown_error_message("unknown"), &[SourcePosition::new(58, 1, 47)], ), RuleError::new( &misplaced_error_message("deprecated", &DirectiveLocation::VariableDefinition), &[SourcePosition::new(98, 2, 30)], ), ], ); } #[test] fn with_many_unknown_directives() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @unknown(directive: "value") { name } human @unknown(directive: "value") { name pets @unknown(directive: "value") { name } } } "#, &[ RuleError::new( &unknown_error_message("unknown"), &[SourcePosition::new(29, 2, 16)], ), RuleError::new( &unknown_error_message("unknown"), &[SourcePosition::new(111, 5, 18)], ), RuleError::new( &unknown_error_message("unknown"), &[SourcePosition::new(180, 7, 19)], ), ], ); } #[test] fn with_well_placed_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo @onQuery { name @include(if: true) ...Frag @include(if: true) skippedField @skip(if: true) ...SkippedFrag @skip(if: true) } mutation Bar @onMutation { someField } "#, ); } #[test] fn with_misplaced_directives() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo @include(if: true) { name @onQuery ...Frag @onQuery } mutation Bar @onQuery { someField } "#, &[ RuleError::new( &misplaced_error_message("include", &DirectiveLocation::Query), &[SourcePosition::new(21, 1, 20)], ), RuleError::new( &misplaced_error_message("onQuery", &DirectiveLocation::Field), &[SourcePosition::new(59, 2, 17)], ), RuleError::new( &misplaced_error_message("onQuery", &DirectiveLocation::FragmentSpread), &[SourcePosition::new(88, 3, 20)], ), RuleError::new( &misplaced_error_message("onQuery", &DirectiveLocation::Mutation), &[SourcePosition::new(133, 6, 23)], ), ], ); } } juniper-0.16.2/src/validation/rules/known_fragment_names.rs000064400000000000000000000050371046102023000222160ustar 00000000000000use crate::{ ast::FragmentSpread, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct KnownFragmentNames; pub fn factory() -> KnownFragmentNames { KnownFragmentNames } impl<'a, S> Visitor<'a, S> for KnownFragmentNames where S: ScalarValue, { fn enter_fragment_spread( &mut self, context: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { let spread_name = &spread.item.name; if !context.is_known_fragment(spread_name.item) { context.report_error(&error_message(spread_name.item), &[spread_name.span.start]); } } } fn error_message(frag_name: &str) -> String { format!(r#"Unknown fragment: "{frag_name}""#) } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn known() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { human(id: 4) { ...HumanFields1 ... on Human { ...HumanFields2 } ... { name } } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } "#, ); } #[test] fn unknown() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { human(id: 4) { ...UnknownFragment1 ... on Human { ...UnknownFragment2 } } } fragment HumanFields on Human { name ...UnknownFragment3 } "#, &[ RuleError::new( &error_message("UnknownFragment1"), &[SourcePosition::new(57, 3, 17)], ), RuleError::new( &error_message("UnknownFragment2"), &[SourcePosition::new(122, 5, 19)], ), RuleError::new( &error_message("UnknownFragment3"), &[SourcePosition::new(255, 11, 15)], ), ], ); } } juniper-0.16.2/src/validation/rules/known_type_names.rs000064400000000000000000000057761046102023000214060ustar 00000000000000use crate::{ ast::{Fragment, InlineFragment, VariableDefinition}, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; use std::fmt::Debug; pub struct KnownTypeNames; pub fn factory() -> KnownTypeNames { KnownTypeNames } impl<'a, S> Visitor<'a, S> for KnownTypeNames where S: ScalarValue, { fn enter_inline_fragment( &mut self, ctx: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) { if let Some(ref type_cond) = fragment.item.type_condition { validate_type(ctx, type_cond.item, &type_cond.span.start); } } fn enter_fragment_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) { let type_cond = &fragment.item.type_condition; validate_type(ctx, type_cond.item, &type_cond.span.start); } fn enter_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, (_, var_def): &'a (Spanning<&'a str>, VariableDefinition), ) { let type_name = var_def.var_type.item.innermost_name(); validate_type(ctx, type_name, &var_def.var_type.span.start); } } fn validate_type( ctx: &mut ValidatorContext<'_, S>, type_name: &str, location: &SourcePosition, ) { if ctx.schema.type_by_name(type_name).is_none() { ctx.report_error(&error_message(type_name), &[*location]); } } fn error_message(type_name: &str) -> String { format!(r#"Unknown type "{type_name}""#) } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn known_type_names_are_valid() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($var: String, $required: [String!]!) { user(id: 4) { pets { ... on Pet { name }, ...PetFields, ... { name } } } } fragment PetFields on Pet { name } "#, ); } #[test] fn unknown_type_names_are_invalid() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($var: JumbledUpLetters) { user(id: 4) { name pets { ... on Badger { name }, ...PetFields } } } fragment PetFields on Peettt { name } "#, &[ RuleError::new( &error_message("JumbledUpLetters"), &[SourcePosition::new(27, 1, 26)], ), RuleError::new(&error_message("Badger"), &[SourcePosition::new(120, 4, 28)]), RuleError::new(&error_message("Peettt"), &[SourcePosition::new(210, 7, 32)]), ], ); } } juniper-0.16.2/src/validation/rules/lone_anonymous_operation.rs000064400000000000000000000064171046102023000231440ustar 00000000000000use crate::{ ast::{Definition, Document, Operation}, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct LoneAnonymousOperation { operation_count: Option, } pub fn factory() -> LoneAnonymousOperation { LoneAnonymousOperation { operation_count: None, } } impl<'a, S> Visitor<'a, S> for LoneAnonymousOperation where S: ScalarValue, { fn enter_document(&mut self, _: &mut ValidatorContext<'a, S>, doc: &'a Document) { self.operation_count = Some( doc.iter() .filter(|d| match **d { Definition::Operation(_) => true, Definition::Fragment(_) => false, }) .count(), ); } fn enter_operation_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { if let Some(operation_count) = self.operation_count { if operation_count > 1 && op.item.name.is_none() { ctx.report_error(error_message(), &[op.span.start]); } } } } fn error_message() -> &'static str { "This anonymous operation must be the only defined operation" } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn no_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Type { field } "#, ); } #[test] fn one_anon_operation() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field } "#, ); } #[test] fn multiple_named_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { field } query Bar { field } "#, ); } #[test] fn anon_operation_with_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { ...Foo } fragment Foo on Type { field } "#, ); } #[test] fn multiple_anon_operations() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { fieldA } { fieldB } "#, &[ RuleError::new(error_message(), &[SourcePosition::new(11, 1, 10)]), RuleError::new(error_message(), &[SourcePosition::new(54, 4, 10)]), ], ); } #[test] fn anon_operation_with_a_mutation() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { fieldA } mutation Foo { fieldB } "#, &[RuleError::new( error_message(), &[SourcePosition::new(11, 1, 10)], )], ); } } juniper-0.16.2/src/validation/rules/mod.rs000064400000000000000000000110161046102023000165650ustar 00000000000000//! Definitions of rules for validation. mod arguments_of_correct_type; mod default_values_of_correct_type; pub mod disable_introspection; mod fields_on_correct_type; mod fragments_on_composite_types; mod known_argument_names; mod known_directives; mod known_fragment_names; mod known_type_names; mod lone_anonymous_operation; mod no_fragment_cycles; mod no_undefined_variables; mod no_unused_fragments; mod no_unused_variables; mod overlapping_fields_can_be_merged; mod possible_fragment_spreads; mod provided_non_null_arguments; mod scalar_leafs; mod unique_argument_names; mod unique_fragment_names; mod unique_input_field_names; mod unique_operation_names; mod unique_variable_names; mod variables_are_input_types; mod variables_in_allowed_position; use std::fmt::Debug; use crate::{ ast::Document, validation::{visit, MultiVisitorNil, ValidatorContext}, value::ScalarValue, }; #[doc(hidden)] pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document) where S: ScalarValue, { // Some validators are depending on the results of other ones. // For example, validators checking fragments usually rely on the fact that // they have no cycles (`no_fragment_cycles`), otherwise may stall in an // infinite recursion. So, we should run validators in stages, moving to the // next stage only once the previous succeeds. This is better than making // every single validator being aware of fragments cycles and/or other // assumptions. let mut stage1 = MultiVisitorNil .with(self::arguments_of_correct_type::factory()) .with(self::default_values_of_correct_type::factory()) .with(self::fields_on_correct_type::factory()) .with(self::fragments_on_composite_types::factory()) .with(self::known_argument_names::factory()) .with(self::known_directives::factory()) .with(self::known_fragment_names::factory()) .with(self::known_type_names::factory()) .with(self::lone_anonymous_operation::factory()) .with(self::no_fragment_cycles::factory()) .with(self::no_undefined_variables::factory()) .with(self::no_unused_fragments::factory()) .with(self::no_unused_variables::factory()) .with(self::possible_fragment_spreads::factory()) .with(self::provided_non_null_arguments::factory()) .with(self::scalar_leafs::factory()) .with(self::unique_argument_names::factory()) .with(self::unique_fragment_names::factory()) .with(self::unique_input_field_names::factory()) .with(self::unique_operation_names::factory()) .with(self::unique_variable_names::factory()) .with(self::variables_are_input_types::factory()) .with(self::variables_in_allowed_position::factory()); visit(&mut stage1, ctx, doc); if ctx.has_errors() { return; } let mut stage2 = MultiVisitorNil.with(self::overlapping_fields_can_be_merged::factory()); visit(&mut stage2, ctx, doc); } #[cfg(test)] mod tests { use crate::{parser::SourcePosition, DefaultScalarValue}; use crate::validation::{expect_fails_fn, RuleError}; #[test] fn handles_recursive_fragments() { expect_fails_fn::<_, DefaultScalarValue>( super::visit_all_rules, "fragment f on QueryRoot { ...f }", &[ RuleError::new( "Fragment \"f\" is never used", &[SourcePosition::new(0, 0, 0)], ), RuleError::new( "Cannot spread fragment \"f\"", &[SourcePosition::new(26, 0, 26)], ), ], ); } #[test] fn handles_nested_recursive_fragments() { expect_fails_fn::<_, DefaultScalarValue>( super::visit_all_rules, "fragment f on QueryRoot { a { ...f a { ...f } } }", &[ RuleError::new( "Fragment \"f\" is never used", &[SourcePosition::new(0, 0, 0)], ), RuleError::new( r#"Unknown field "a" on type "QueryRoot""#, &[SourcePosition::new(26, 0, 26)], ), RuleError::new( "Cannot spread fragment \"f\"", &[SourcePosition::new(30, 0, 30)], ), RuleError::new( "Cannot spread fragment \"f\"", &[SourcePosition::new(39, 0, 39)], ), ], ); } } juniper-0.16.2/src/validation/rules/no_fragment_cycles.rs000064400000000000000000000260161046102023000216550ustar 00000000000000use std::collections::{HashMap, HashSet}; use crate::{ ast::{Document, Fragment, FragmentSpread}, parser::Spanning, validation::{RuleError, ValidatorContext, Visitor}, value::ScalarValue, Span, }; pub fn factory<'a>() -> NoFragmentCycles<'a> { NoFragmentCycles { current_fragment: None, spreads: HashMap::new(), fragment_order: Vec::new(), } } type BorrowedSpanning<'a, T> = Spanning<&'a T, &'a Span>; pub struct NoFragmentCycles<'a> { current_fragment: Option<&'a str>, spreads: HashMap<&'a str, Vec>>, fragment_order: Vec<&'a str>, } impl<'a, S> Visitor<'a, S> for NoFragmentCycles<'a> where S: ScalarValue, { fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, _: &'a Document) { assert!(self.current_fragment.is_none()); let mut detector = CycleDetector { visited: HashSet::new(), spreads: &self.spreads, errors: Vec::new(), }; for frag in &self.fragment_order { if !detector.visited.contains(frag) { detector.detect_from(frag); } } ctx.append_errors(detector.errors); } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) { assert!(self.current_fragment.is_none()); let fragment_name = &fragment.item.name.item; self.current_fragment = Some(fragment_name); self.fragment_order.push(fragment_name); } fn exit_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) { assert_eq!(Some(fragment.item.name.item), self.current_fragment); self.current_fragment = None; } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let Some(current_fragment) = self.current_fragment { self.spreads .entry(current_fragment) .or_default() .push(BorrowedSpanning { item: spread.item.name.item, span: &spread.span, }); } } } type CycleDetectorState<'a> = ( &'a str, Vec<&'a BorrowedSpanning<'a, str>>, HashMap<&'a str, usize>, ); struct CycleDetector<'a> { visited: HashSet<&'a str>, spreads: &'a HashMap<&'a str, Vec>>, errors: Vec, } impl<'a> CycleDetector<'a> { fn detect_from(&mut self, from: &'a str) { 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 BorrowedSpanning<'a, str>>, mut path_indices: HashMap<&'a str, usize>, ) -> Vec> { self.visited.insert(from); if !self.spreads.contains_key(from) { return Vec::new(); } path_indices.insert(from, path.len()); let mut to_visit = Vec::new(); for node in &self.spreads[from] { let name = node.item; let index = path_indices.get(name).cloned(); if let Some(index) = index { let err_pos = if index < path.len() { path[index] } else { node }; self.errors .push(RuleError::new(&error_message(name), &[err_pos.span.start])); } else { let mut path = path.clone(); path.push(node); to_visit.push((name, path, path_indices.clone())); } } to_visit } } fn error_message(frag_name: &str) -> String { format!(r#"Cannot spread fragment "{frag_name}""#) } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn single_reference_is_valid() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB } fragment fragB on Dog { name } "#, ); } #[test] fn spreading_twice_is_not_circular() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB, ...fragB } fragment fragB on Dog { name } "#, ); } #[test] fn spreading_twice_indirectly_is_not_circular() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB, ...fragC } fragment fragB on Dog { ...fragC } fragment fragC on Dog { name } "#, ); } #[test] fn double_spread_within_abstract_types() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment nameFragment on Pet { ... on Dog { name } ... on Cat { name } } fragment spreadsInAnon on Pet { ... on Dog { ...nameFragment } ... on Cat { ...nameFragment } } "#, ); } #[test] fn does_not_false_positive_on_unknown_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment nameFragment on Pet { ...UnknownFragment } "#, ); } #[test] fn spreading_recursively_within_field_fails() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Human { relatives { ...fragA } }, "#, &[RuleError::new( &error_message("fragA"), &[SourcePosition::new(49, 1, 48)], )], ); } #[test] fn no_spreading_itself_directly() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragA } "#, &[RuleError::new( &error_message("fragA"), &[SourcePosition::new(35, 1, 34)], )], ); } #[test] fn no_spreading_itself_directly_within_inline_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Pet { ... on Dog { ...fragA } } "#, &[RuleError::new( &error_message("fragA"), &[SourcePosition::new(74, 3, 14)], )], ); } #[test] fn no_spreading_itself_indirectly() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragA } "#, &[RuleError::new( &error_message("fragA"), &[SourcePosition::new(35, 1, 34)], )], ); } #[test] fn no_spreading_itself_indirectly_reports_opposite_order() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragB on Dog { ...fragA } fragment fragA on Dog { ...fragB } "#, &[RuleError::new( &error_message("fragB"), &[SourcePosition::new(35, 1, 34)], )], ); } #[test] fn no_spreading_itself_indirectly_within_inline_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Pet { ... on Dog { ...fragB } } fragment fragB on Pet { ... on Dog { ...fragA } } "#, &[RuleError::new( &error_message("fragA"), &[SourcePosition::new(74, 3, 14)], )], ); } #[test] fn no_spreading_itself_deeply() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragC } fragment fragC on Dog { ...fragO } fragment fragX on Dog { ...fragY } fragment fragY on Dog { ...fragZ } fragment fragZ on Dog { ...fragO } fragment fragO on Dog { ...fragP } fragment fragP on Dog { ...fragA, ...fragX } "#, &[ RuleError::new(&error_message("fragA"), &[SourcePosition::new(35, 1, 34)]), RuleError::new(&error_message("fragO"), &[SourcePosition::new(305, 7, 34)]), ], ); } #[test] fn no_spreading_itself_deeply_two_paths() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB, ...fragC } fragment fragB on Dog { ...fragA } fragment fragC on Dog { ...fragA } "#, &[ RuleError::new(&error_message("fragA"), &[SourcePosition::new(35, 1, 34)]), RuleError::new(&error_message("fragA"), &[SourcePosition::new(45, 1, 44)]), ], ); } #[test] fn no_spreading_itself_deeply_two_paths_alt_traversal_order() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragC } fragment fragB on Dog { ...fragC } fragment fragC on Dog { ...fragA, ...fragB } "#, &[ RuleError::new(&error_message("fragA"), &[SourcePosition::new(35, 1, 34)]), RuleError::new(&error_message("fragC"), &[SourcePosition::new(135, 3, 44)]), ], ); } #[test] fn no_spreading_itself_deeply_and_immediately() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { ...fragB } fragment fragB on Dog { ...fragB, ...fragC } fragment fragC on Dog { ...fragA, ...fragB } "#, &[ RuleError::new(&error_message("fragA"), &[SourcePosition::new(35, 1, 34)]), RuleError::new(&error_message("fragB"), &[SourcePosition::new(80, 2, 34)]), RuleError::new(&error_message("fragB"), &[SourcePosition::new(90, 2, 44)]), ], ); } } juniper-0.16.2/src/validation/rules/no_undefined_variables.rs000064400000000000000000000437331046102023000225060ustar 00000000000000use crate::{ ast::{Document, Fragment, FragmentSpread, InputValue, Operation, VariableDefinition}, parser::{SourcePosition, Spanning}, validation::{RuleError, ValidatorContext, Visitor}, value::ScalarValue, Span, }; use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Scope<'a> { Operation(Option<&'a str>), Fragment(&'a str), } pub fn factory<'a>() -> NoUndefinedVariables<'a> { NoUndefinedVariables { defined_variables: HashMap::new(), used_variables: HashMap::new(), current_scope: None, spreads: HashMap::new(), } } type BorrowedSpanning<'a, T> = Spanning<&'a T, &'a Span>; pub struct NoUndefinedVariables<'a> { defined_variables: HashMap, (SourcePosition, HashSet<&'a str>)>, used_variables: HashMap, Vec>>, current_scope: Option>, spreads: HashMap, Vec<&'a str>>, } impl<'a> NoUndefinedVariables<'a> { fn find_undef_vars( &'a self, scope: &Scope<'a>, defined: &HashSet<&'a str>, unused: &mut Vec>, visited: &mut HashSet>, ) { let mut to_visit = Vec::new(); if let Some(spreads) = self.find_undef_vars_inner(scope, defined, unused, visited) { to_visit.push(spreads); } while let Some(spreads) = to_visit.pop() { for spread in spreads { if let Some(spreads) = self.find_undef_vars_inner(&Scope::Fragment(spread), defined, unused, visited) { to_visit.push(spreads); } } } } /// This function should be called only inside [`Self::find_undef_vars()`], /// as it's a recursive function using heap instead of a stack. So, instead /// of the recursive call, we return a [`Vec`] that is visited inside /// [`Self::find_undef_vars()`]. fn find_undef_vars_inner( &'a self, scope: &Scope<'a>, defined: &HashSet<&'a str>, unused: &mut Vec>, visited: &mut HashSet>, ) -> Option<&'a Vec<&'a str>> { if visited.contains(scope) { return None; } visited.insert(scope.clone()); if let Some(used_vars) = self.used_variables.get(scope) { for var in used_vars { if !defined.contains(&var.item) { unused.push(*var); } } } self.spreads.get(scope) } } impl<'a, S> Visitor<'a, S> for NoUndefinedVariables<'a> where S: ScalarValue, { fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, _: &'a Document) { for (op_name, (pos, def_vars)) in &self.defined_variables { let mut unused = Vec::new(); let mut visited = HashSet::new(); self.find_undef_vars( &Scope::Operation(*op_name), def_vars, &mut unused, &mut visited, ); ctx.append_errors( unused .into_iter() .map(|var| { RuleError::new(&error_message(var.item, *op_name), &[var.span.start, *pos]) }) .collect(), ); } } fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { let op_name = op.item.name.as_ref().map(|s| s.item); self.current_scope = Some(Scope::Operation(op_name)); self.defined_variables .insert(op_name, (op.span.start, HashSet::new())); } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.current_scope = Some(Scope::Fragment(f.item.name.item)); } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let Some(ref scope) = self.current_scope { self.spreads .entry(scope.clone()) .or_default() .push(spread.item.name.item); } } fn enter_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, (var_name, _): &'a (Spanning<&'a str>, VariableDefinition), ) { if let Some(Scope::Operation(ref name)) = self.current_scope { if let Some(&mut (_, ref mut vars)) = self.defined_variables.get_mut(name) { vars.insert(var_name.item); } } } fn enter_argument( &mut self, _: &mut ValidatorContext<'a, S>, (_, value): &'a (Spanning<&'a str>, Spanning>), ) { if let Some(ref scope) = self.current_scope { self.used_variables .entry(scope.clone()) .or_default() .append( &mut value .item .referenced_variables() .iter() .map(|&var_name| BorrowedSpanning { span: &value.span, item: var_name, }) .collect(), ); } } } fn error_message(var_name: &str, op_name: Option<&str>) -> String { if let Some(op_name) = op_name { format!(r#"Variable "${var_name}" is not defined by operation "{op_name}""#) } else { format!(r#"Variable "${var_name}" is not defined"#) } } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn all_variables_defined() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c) } "#, ); } #[test] fn all_variables_deeply_defined() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { field(a: $a) { field(b: $b) { field(c: $c) } } } "#, ); } #[test] fn all_variables_deeply_defined_in_inline_fragments_defined() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ... on Type { field(a: $a) { field(b: $b) { ... on Type { field(c: $c) } } } } } "#, ); } #[test] fn all_variables_in_fragments_deeply_defined() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } "#, ); } #[test] fn variable_within_single_fragment_defined_in_multiple_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragA } query Bar($a: String) { ...FragA } fragment FragA on Type { field(a: $a) } "#, ); } #[test] fn variable_within_fragments_defined_in_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragA } query Bar($b: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } "#, ); } #[test] fn variable_within_recursive_fragment_defined() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragA } } "#, ); } #[test] fn variable_not_defined() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c, d: $d) } "#, &[RuleError::new( &error_message("d", Some("Foo")), &[ SourcePosition::new(101, 2, 42), SourcePosition::new(11, 1, 10), ], )], ); } #[test] fn variable_not_defined_by_unnamed_query() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field(a: $a) } "#, &[RuleError::new( &error_message("a", None), &[ SourcePosition::new(34, 2, 21), SourcePosition::new(11, 1, 10), ], )], ); } #[test] fn multiple_variables_not_defined() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { field(a: $a, b: $b, c: $c) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(56, 2, 21), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("c", Some("Foo")), &[ SourcePosition::new(70, 2, 35), SourcePosition::new(11, 1, 10), ], ), ], ); } #[test] fn variable_in_fragment_not_defined_by_unnamed_query() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { ...FragA } fragment FragA on Type { field(a: $a) } "#, &[RuleError::new( &error_message("a", None), &[ SourcePosition::new(102, 5, 21), SourcePosition::new(11, 1, 10), ], )], ); } #[test] fn variable_in_fragment_not_defined_by_operation() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } "#, &[RuleError::new( &error_message("c", Some("Foo")), &[ SourcePosition::new(358, 15, 21), SourcePosition::new(11, 1, 10), ], )], ); } #[test] fn multiple_variables_in_fragments_not_defined() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(124, 5, 21), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("c", Some("Foo")), &[ SourcePosition::new(346, 15, 21), SourcePosition::new(11, 1, 10), ], ), ], ); } #[test] fn single_variable_in_fragment_not_defined_by_multiple_operations() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field(a: $a, b: $b) } "#, &[ RuleError::new( &error_message("b", Some("Foo")), &[ SourcePosition::new(201, 8, 28), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("b", Some("Bar")), &[ SourcePosition::new(201, 8, 28), SourcePosition::new(79, 4, 10), ], ), ], ); } #[test] fn variables_in_fragment_not_defined_by_multiple_operations() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field(a: $a, b: $b) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(194, 8, 21), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("b", Some("Bar")), &[ SourcePosition::new(201, 8, 28), SourcePosition::new(79, 4, 10), ], ), ], ); } #[test] fn variable_in_fragment_used_by_other_operation() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragA } query Bar($a: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(191, 8, 21), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("b", Some("Bar")), &[ SourcePosition::new(263, 11, 21), SourcePosition::new(78, 4, 10), ], ), ], ); } #[test] fn multiple_undefined_variables_produce_multiple_errors() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragAB } query Bar($a: String) { ...FragAB } fragment FragAB on Type { field1(a: $a, b: $b) ...FragC field3(a: $a, b: $b) } fragment FragC on Type { field2(c: $c) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(195, 8, 22), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("b", Some("Bar")), &[ SourcePosition::new(202, 8, 29), SourcePosition::new(79, 4, 10), ], ), RuleError::new( &error_message("a", Some("Foo")), &[ SourcePosition::new(249, 10, 22), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("b", Some("Bar")), &[ SourcePosition::new(256, 10, 29), SourcePosition::new(79, 4, 10), ], ), RuleError::new( &error_message("c", Some("Foo")), &[ SourcePosition::new(329, 13, 22), SourcePosition::new(11, 1, 10), ], ), RuleError::new( &error_message("c", Some("Bar")), &[ SourcePosition::new(329, 13, 22), SourcePosition::new(79, 4, 10), ], ), ], ); } } juniper-0.16.2/src/validation/rules/no_unused_fragments.rs000064400000000000000000000174711046102023000220660ustar 00000000000000use std::collections::{HashMap, HashSet}; use crate::{ ast::{Definition, Document, Fragment, FragmentSpread, Operation}, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, Span, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Scope<'a> { Operation(Option<&'a str>), Fragment(&'a str), } pub fn factory<'a>() -> NoUnusedFragments<'a> { NoUnusedFragments { spreads: HashMap::new(), defined_fragments: HashSet::new(), current_scope: None, } } type BorrowedSpanning<'a, T> = Spanning<&'a T, &'a Span>; pub struct NoUnusedFragments<'a> { spreads: HashMap, Vec<&'a str>>, defined_fragments: HashSet>, current_scope: Option>, } impl<'a> NoUnusedFragments<'a> { fn find_reachable_fragments(&'a self, from: Scope<'a>, result: &mut HashSet<&'a str>) { let mut to_visit = Vec::new(); to_visit.push(from); while let Some(from) = to_visit.pop() { if let Some(next) = self.find_reachable_fragments_inner(from, result) { to_visit.extend(next.iter().map(|s| Scope::Fragment(s))); } } } /// This function should be called only inside /// [`Self::find_reachable_fragments()`], as it's a recursive function using /// heap instead of a stack. So, instead of the recursive call, we return a /// [`Vec`] that is visited inside [`Self::find_reachable_fragments()`]. fn find_reachable_fragments_inner( &'a self, from: Scope<'a>, result: &mut HashSet<&'a str>, ) -> Option<&'a Vec<&'a str>> { if let Scope::Fragment(name) = from { if result.contains(name) { return None; } else { result.insert(name); } } self.spreads.get(&from) } } impl<'a, S> Visitor<'a, S> for NoUnusedFragments<'a> where S: ScalarValue, { fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, defs: &'a Document) { let mut reachable = HashSet::new(); for def in defs { if let Definition::Operation(Spanning { item: Operation { ref name, .. }, .. }) = *def { let op_name = name.as_ref().map(|s| s.item); self.find_reachable_fragments(Scope::Operation(op_name), &mut reachable); } } for fragment in &self.defined_fragments { if !reachable.contains(&fragment.item) { ctx.report_error(&error_message(fragment.item), &[fragment.span.start]); } } } fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { let op_name = op.item.name.as_ref().map(|s| s.item); self.current_scope = Some(Scope::Operation(op_name)); } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.current_scope = Some(Scope::Fragment(f.item.name.item)); self.defined_fragments.insert(BorrowedSpanning { span: &f.span, item: f.item.name.item, }); } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let Some(ref scope) = self.current_scope { self.spreads .entry(*scope) .or_default() .push(spread.item.name.item); } } } fn error_message(frag_name: &str) -> String { format!(r#"Fragment "{frag_name}" is never used"#) } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn all_fragment_names_are_used() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { human(id: 4) { ...HumanFields1 ... on Human { ...HumanFields2 } } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } "#, ); } #[test] fn all_fragment_names_are_used_by_multiple_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } "#, ); } #[test] fn contains_unknown_fragments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } fragment Unused1 on Human { name } fragment Unused2 on Human { name } "#, &[ RuleError::new( &error_message("Unused1"), &[SourcePosition::new(465, 21, 10)], ), RuleError::new( &error_message("Unused2"), &[SourcePosition::new(532, 24, 10)], ), ], ); } #[test] fn contains_unknown_fragments_with_ref_cycle() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { human(id: 4) { ...HumanFields1 } } query Bar { human(id: 4) { ...HumanFields2 } } fragment HumanFields1 on Human { name ...HumanFields3 } fragment HumanFields2 on Human { name } fragment HumanFields3 on Human { name } fragment Unused1 on Human { name ...Unused2 } fragment Unused2 on Human { name ...Unused1 } "#, &[ RuleError::new( &error_message("Unused1"), &[SourcePosition::new(465, 21, 10)], ), RuleError::new( &error_message("Unused2"), &[SourcePosition::new(555, 25, 10)], ), ], ); } #[test] fn contains_unknown_and_undef_fragments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { human(id: 4) { ...bar } } fragment foo on Human { name } "#, &[RuleError::new( &error_message("foo"), &[SourcePosition::new(107, 6, 10)], )], ); } } juniper-0.16.2/src/validation/rules/no_unused_variables.rs000064400000000000000000000274071046102023000220500ustar 00000000000000use crate::{ ast::{Document, Fragment, FragmentSpread, InputValue, Operation, VariableDefinition}, parser::Spanning, validation::{RuleError, ValidatorContext, Visitor}, value::ScalarValue, }; use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Scope<'a> { Operation(Option<&'a str>), Fragment(&'a str), } pub fn factory<'a>() -> NoUnusedVariables<'a> { NoUnusedVariables { defined_variables: HashMap::new(), used_variables: HashMap::new(), current_scope: None, spreads: HashMap::new(), } } pub struct NoUnusedVariables<'a> { defined_variables: HashMap, HashSet<&'a Spanning<&'a str>>>, used_variables: HashMap, Vec<&'a str>>, current_scope: Option>, spreads: HashMap, Vec<&'a str>>, } impl<'a> NoUnusedVariables<'a> { fn find_used_vars( &'a self, from: &Scope<'a>, defined: &HashSet<&'a str>, used: &mut HashSet<&'a str>, visited: &mut HashSet>, ) { let mut to_visit = Vec::new(); if let Some(spreads) = self.find_used_vars_inner(from, defined, used, visited) { to_visit.push(spreads); } while let Some(spreads) = to_visit.pop() { for spread in spreads { if let Some(spreads) = self.find_used_vars_inner(&Scope::Fragment(spread), defined, used, visited) { to_visit.push(spreads); } } } } /// This function should be called only inside [`Self::find_used_vars()`], /// as it's a recursive function using heap instead of a stack. So, instead /// of the recursive call, we return a [`Vec`] that is visited inside /// [`Self::find_used_vars()`]. fn find_used_vars_inner( &'a self, from: &Scope<'a>, defined: &HashSet<&'a str>, used: &mut HashSet<&'a str>, visited: &mut HashSet>, ) -> Option<&'a Vec<&'a str>> { if visited.contains(from) { return None; } visited.insert(from.clone()); if let Some(used_vars) = self.used_variables.get(from) { for var in used_vars { if defined.contains(var) { used.insert(var); } } } self.spreads.get(from) } } impl<'a, S> Visitor<'a, S> for NoUnusedVariables<'a> where S: ScalarValue, { fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, _: &'a Document) { for (op_name, def_vars) in &self.defined_variables { let mut used = HashSet::new(); let mut visited = HashSet::new(); self.find_used_vars( &Scope::Operation(*op_name), &def_vars.iter().map(|def| def.item).collect(), &mut used, &mut visited, ); ctx.append_errors( def_vars .iter() .filter(|var| !used.contains(var.item)) .map(|var| { RuleError::new(&error_message(var.item, *op_name), &[var.span.start]) }) .collect(), ); } } fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { let op_name = op.item.name.as_ref().map(|s| s.item); self.current_scope = Some(Scope::Operation(op_name)); self.defined_variables.insert(op_name, HashSet::new()); } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { self.current_scope = Some(Scope::Fragment(f.item.name.item)); } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let Some(ref scope) = self.current_scope { self.spreads .entry(scope.clone()) .or_default() .push(spread.item.name.item); } } fn enter_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, (var_name, _): &'a (Spanning<&'a str>, VariableDefinition), ) { if let Some(Scope::Operation(ref name)) = self.current_scope { if let Some(vars) = self.defined_variables.get_mut(name) { vars.insert(var_name); } } } fn enter_argument( &mut self, _: &mut ValidatorContext<'a, S>, (_, value): &'a (Spanning<&'a str>, Spanning>), ) { if let Some(ref scope) = self.current_scope { self.used_variables .entry(scope.clone()) .or_default() .append(&mut value.item.referenced_variables()); } } } fn error_message(var_name: &str, op_name: Option<&str>) -> String { if let Some(op_name) = op_name { format!(r#"Variable "${var_name}" is not used by operation "{op_name}""#) } else { format!(r#"Variable "${var_name}" is not used"#) } } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn uses_all_variables() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query ($a: String, $b: String, $c: String) { field(a: $a, b: $b, c: $c) } "#, ); } #[test] fn uses_all_variables_deeply() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { field(a: $a) { field(b: $b) { field(c: $c) } } } "#, ); } #[test] fn uses_all_variables_deeply_in_inline_fragments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ... on Type { field(a: $a) { field(b: $b) { ... on Type { field(c: $c) } } } } } "#, ); } #[test] fn uses_all_variables_in_fragments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field(c: $c) } "#, ); } #[test] fn variable_used_by_fragment_in_multiple_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragA } query Bar($b: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } "#, ); } #[test] fn variable_used_by_recursive_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragA } } "#, ); } #[test] fn variable_not_used() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query ($a: String, $b: String, $c: String) { field(a: $a, b: $b) } "#, &[RuleError::new( &error_message("c", None), &[SourcePosition::new(42, 1, 41)], )], ); } #[test] fn multiple_variables_not_used_1() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { field(b: $b) } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[SourcePosition::new(21, 1, 20)], ), RuleError::new( &error_message("c", Some("Foo")), &[SourcePosition::new(45, 1, 44)], ), ], ); } #[test] fn variable_not_used_in_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field(a: $a) { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field } "#, &[RuleError::new( &error_message("c", Some("Foo")), &[SourcePosition::new(45, 1, 44)], )], ); } #[test] fn multiple_variables_not_used_2() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: String, $c: String) { ...FragA } fragment FragA on Type { field { ...FragB } } fragment FragB on Type { field(b: $b) { ...FragC } } fragment FragC on Type { field } "#, &[ RuleError::new( &error_message("a", Some("Foo")), &[SourcePosition::new(21, 1, 20)], ), RuleError::new( &error_message("c", Some("Foo")), &[SourcePosition::new(45, 1, 44)], ), ], ); } #[test] fn variable_not_used_by_unreferenced_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragA } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } "#, &[RuleError::new( &error_message("b", Some("Foo")), &[SourcePosition::new(21, 1, 20)], )], ); } #[test] fn variable_not_used_by_fragment_used_by_other_operation() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($b: String) { ...FragA } query Bar($a: String) { ...FragB } fragment FragA on Type { field(a: $a) } fragment FragB on Type { field(b: $b) } "#, &[ RuleError::new( &error_message("b", Some("Foo")), &[SourcePosition::new(21, 1, 20)], ), RuleError::new( &error_message("a", Some("Bar")), &[SourcePosition::new(88, 4, 20)], ), ], ); } } juniper-0.16.2/src/validation/rules/overlapping_fields_can_be_merged.rs000064400000000000000000001765001046102023000245060ustar 00000000000000use std::{borrow::Borrow, cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash}; use crate::{ ast::{Arguments, Definition, Document, Field, Fragment, FragmentSpread, Selection, Type}, parser::{SourcePosition, Spanning}, schema::meta::{Field as FieldType, MetaType}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; #[derive(Debug)] struct Conflict(ConflictReason, Vec, Vec); #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct ConflictReason(String, ConflictReasonMessage); #[derive(Debug)] struct AstAndDef<'a, S: Debug + 'a>( Option<&'a str>, &'a Spanning>, Option<&'a FieldType<'a, S>>, ); type AstAndDefCollection<'a, S> = OrderedMap<&'a str, Vec>>; #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum ConflictReasonMessage { Message(String), Nested(Vec), } struct PairSet<'a> { data: HashMap<&'a str, HashMap<&'a str, bool>>, } #[derive(Debug)] struct OrderedMap { data: HashMap, insert_order: Vec, } struct OrderedMapIter<'a, K: 'a, V: 'a> { map: &'a HashMap, inner: ::std::slice::Iter<'a, K>, } impl OrderedMap { fn new() -> OrderedMap { OrderedMap { data: HashMap::new(), insert_order: Vec::new(), } } fn iter(&self) -> OrderedMapIter { OrderedMapIter { map: &self.data, inner: self.insert_order.iter(), } } fn get(&self, k: &Q) -> Option<&V> where K: Borrow, Q: Hash + Eq, { self.data.get(k) } fn get_mut(&mut self, k: &Q) -> Option<&mut V> where K: Borrow, Q: Hash + Eq, { self.data.get_mut(k) } fn contains_key(&self, k: &Q) -> bool where K: Borrow, Q: Hash + Eq, { self.data.contains_key(k) } fn insert(&mut self, k: K, v: V) -> Option { let result = self.data.insert(k.clone(), v); if result.is_none() { self.insert_order.push(k); } result } } impl<'a, K: Eq + Hash + 'a, V: 'a> Iterator for OrderedMapIter<'a, K, V> { type Item = (&'a K, &'a V); fn next(&mut self) -> Option { self.inner .next() .and_then(|key| self.map.get(key).map(|value| (key, value))) } } impl<'a> PairSet<'a> { fn new() -> PairSet<'a> { PairSet { data: HashMap::new(), } } fn contains(&self, a: &'a str, b: &'a str, mutex: bool) -> bool { if let Some(result) = self.data.get(a).and_then(|s| s.get(b)) { if !mutex { !result } else { true } } else { false } } fn insert(&mut self, a: &'a str, b: &'a str, mutex: bool) { self.data.entry(a).or_default().insert(b, mutex); self.data.entry(b).or_default().insert(a, mutex); } } pub struct OverlappingFieldsCanBeMerged<'a, S: Debug + 'a> { named_fragments: HashMap<&'a str, &'a Fragment<'a, S>>, compared_fragments: RefCell>, } pub fn factory<'a, S: Debug>() -> OverlappingFieldsCanBeMerged<'a, S> { OverlappingFieldsCanBeMerged { named_fragments: HashMap::new(), compared_fragments: RefCell::new(PairSet::new()), } } impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> { fn find_conflicts_within_selection_set( &self, parent_type: Option<&'a MetaType>, selection_set: &'a [Selection], ctx: &ValidatorContext<'a, S>, ) -> Vec where S: ScalarValue, { let mut conflicts = Vec::new(); let (field_map, fragment_names) = self.get_fields_and_fragment_names(parent_type, selection_set, ctx); self.collect_conflicts_within(&mut conflicts, &field_map, ctx); for (i, frag_name1) in fragment_names.iter().enumerate() { self.collect_conflicts_between_fields_and_fragment( &mut conflicts, &field_map, frag_name1, false, ctx, ); for frag_name2 in &fragment_names[i + 1..] { self.collect_conflicts_between_fragments( &mut conflicts, frag_name1, frag_name2, false, ctx, ); } } conflicts } fn collect_conflicts_between_fragments( &self, conflicts: &mut Vec, fragment_name1: &'a str, fragment_name2: &'a str, mutually_exclusive: bool, ctx: &ValidatorContext<'a, S>, ) where S: ScalarValue, { // Early return on fragment recursion, as it makes no sense. // Fragment recursions are prevented by `no_fragment_cycles` validator. if fragment_name1 == fragment_name2 { return; } let fragment1 = match self.named_fragments.get(fragment_name1) { Some(f) => f, None => return, }; let fragment2 = match self.named_fragments.get(fragment_name2) { Some(f) => f, None => return, }; { if self.compared_fragments.borrow().contains( fragment_name1, fragment_name2, mutually_exclusive, ) { return; } } { self.compared_fragments.borrow_mut().insert( fragment_name1, fragment_name2, mutually_exclusive, ); } let (field_map1, fragment_names1) = self.get_referenced_fields_and_fragment_names(fragment1, ctx); let (field_map2, fragment_names2) = self.get_referenced_fields_and_fragment_names(fragment2, ctx); self.collect_conflicts_between( conflicts, mutually_exclusive, &field_map1, &field_map2, ctx, ); for fragment_name2 in &fragment_names2 { self.collect_conflicts_between_fragments( conflicts, fragment_name1, fragment_name2, mutually_exclusive, ctx, ); } for fragment_name1 in &fragment_names1 { self.collect_conflicts_between_fragments( conflicts, fragment_name1, fragment_name2, mutually_exclusive, ctx, ); } } fn collect_conflicts_between_fields_and_fragment( &self, conflicts: &mut Vec, field_map: &AstAndDefCollection<'a, S>, fragment_name: &str, mutually_exclusive: bool, ctx: &ValidatorContext<'a, S>, ) where S: ScalarValue, { let mut to_check = Vec::new(); if let Some(fragments) = self.collect_conflicts_between_fields_and_fragment_inner( conflicts, field_map, fragment_name, mutually_exclusive, ctx, ) { to_check.push((fragment_name, fragments)) } while let Some((fragment_name, fragment_names2)) = to_check.pop() { for fragment_name2 in fragment_names2 { // Early return on fragment recursion, as it makes no sense. // Fragment recursions are prevented by `no_fragment_cycles` validator. if fragment_name == fragment_name2 { return; } if let Some(fragments) = self.collect_conflicts_between_fields_and_fragment_inner( conflicts, field_map, fragment_name2, mutually_exclusive, ctx, ) { to_check.push((fragment_name2, fragments)); }; } } } /// This function should be called only inside /// [`Self::collect_conflicts_between_fields_and_fragment()`], as it's a /// recursive function using heap instead of a stack. So, instead of the /// recursive call, we return a [`Vec`] that is visited inside /// [`Self::collect_conflicts_between_fields_and_fragment()`]. fn collect_conflicts_between_fields_and_fragment_inner( &self, conflicts: &mut Vec, field_map: &AstAndDefCollection<'a, S>, fragment_name: &str, mutually_exclusive: bool, ctx: &ValidatorContext<'a, S>, ) -> Option> where S: ScalarValue, { let fragment = self.named_fragments.get(fragment_name)?; let (field_map2, fragment_names2) = self.get_referenced_fields_and_fragment_names(fragment, ctx); self.collect_conflicts_between(conflicts, mutually_exclusive, field_map, &field_map2, ctx); Some(fragment_names2) } fn collect_conflicts_between( &self, conflicts: &mut Vec, mutually_exclusive: bool, field_map1: &AstAndDefCollection<'a, S>, field_map2: &AstAndDefCollection<'a, S>, ctx: &ValidatorContext<'a, S>, ) where S: ScalarValue, { for (response_name, fields1) in field_map1.iter() { if let Some(fields2) = field_map2.get(response_name) { for field1 in fields1 { for field2 in fields2 { if let Some(conflict) = self.find_conflict( response_name, field1, field2, mutually_exclusive, ctx, ) { conflicts.push(conflict); } } } } } } fn collect_conflicts_within( &self, conflicts: &mut Vec, field_map: &AstAndDefCollection<'a, S>, ctx: &ValidatorContext<'a, S>, ) where S: ScalarValue, { for (response_name, fields) in field_map.iter() { for (i, field1) in fields.iter().enumerate() { for field2 in &fields[i + 1..] { if let Some(conflict) = self.find_conflict(response_name, field1, field2, false, ctx) { conflicts.push(conflict); } } } } } fn find_conflict( &self, response_name: &str, field1: &AstAndDef<'a, S>, field2: &AstAndDef<'a, S>, parents_mutually_exclusive: bool, ctx: &ValidatorContext<'a, S>, ) -> Option where S: ScalarValue, { let AstAndDef(ref parent_type1, ast1, ref def1) = *field1; let AstAndDef(ref parent_type2, ast2, ref def2) = *field2; let mutually_exclusive = parents_mutually_exclusive || (parent_type1 != parent_type2 && self.is_object_type(ctx, *parent_type1) && self.is_object_type(ctx, *parent_type2)); if !mutually_exclusive { let name1 = &ast1.item.name.item; let name2 = &ast2.item.name.item; if name1 != name2 { return Some(Conflict( ConflictReason( response_name.into(), ConflictReasonMessage::Message(format!( "{name1} and {name2} are different fields", )), ), vec![ast1.span.start], vec![ast2.span.start], )); } if !self.is_same_arguments(&ast1.item.arguments, &ast2.item.arguments) { return Some(Conflict( ConflictReason( response_name.into(), ConflictReasonMessage::Message("they have differing arguments".into()), ), vec![ast1.span.start], vec![ast2.span.start], )); } } let t1 = def1.as_ref().map(|def| &def.field_type); let t2 = def2.as_ref().map(|def| &def.field_type); if let (Some(t1), Some(t2)) = (t1, t2) { if Self::is_type_conflict(ctx, t1, t2) { return Some(Conflict( ConflictReason( response_name.into(), ConflictReasonMessage::Message(format!( "they return conflicting types {t1} and {t2}", )), ), vec![ast1.span.start], vec![ast2.span.start], )); } } if let (Some(s1), Some(s2)) = (&ast1.item.selection_set, &ast2.item.selection_set) { let conflicts = self.find_conflicts_between_sub_selection_sets( mutually_exclusive, t1.map(Type::innermost_name), s1, t2.map(Type::innermost_name), s2, ctx, ); return self.subfield_conflicts( &conflicts, response_name, &ast1.span.start, &ast2.span.start, ); } None } fn find_conflicts_between_sub_selection_sets( &self, mutually_exclusive: bool, parent_type1: Option<&str>, selection_set1: &'a [Selection], parent_type2: Option<&str>, selection_set2: &'a [Selection], ctx: &ValidatorContext<'a, S>, ) -> Vec where S: ScalarValue, { let mut conflicts = Vec::new(); let parent_type1 = parent_type1.and_then(|t| ctx.schema.concrete_type_by_name(t)); let parent_type2 = parent_type2.and_then(|t| ctx.schema.concrete_type_by_name(t)); let (field_map1, fragment_names1) = self.get_fields_and_fragment_names(parent_type1, selection_set1, ctx); let (field_map2, fragment_names2) = self.get_fields_and_fragment_names(parent_type2, selection_set2, ctx); self.collect_conflicts_between( &mut conflicts, mutually_exclusive, &field_map1, &field_map2, ctx, ); for fragment_name in &fragment_names2 { self.collect_conflicts_between_fields_and_fragment( &mut conflicts, &field_map1, fragment_name, mutually_exclusive, ctx, ); } for fragment_name in &fragment_names1 { self.collect_conflicts_between_fields_and_fragment( &mut conflicts, &field_map2, fragment_name, mutually_exclusive, ctx, ); } for fragment_name1 in &fragment_names1 { for fragment_name2 in &fragment_names2 { self.collect_conflicts_between_fragments( &mut conflicts, fragment_name1, fragment_name2, mutually_exclusive, ctx, ); } } conflicts } fn subfield_conflicts( &self, conflicts: &[Conflict], response_name: &str, pos1: &SourcePosition, pos2: &SourcePosition, ) -> Option { if conflicts.is_empty() { return None; } Some(Conflict( ConflictReason( response_name.into(), ConflictReasonMessage::Nested(conflicts.iter().map(|c| c.0.clone()).collect()), ), vec![*pos1] .into_iter() .chain(conflicts.iter().flat_map(|Conflict(_, fs1, _)| fs1.clone())) .collect(), vec![*pos2] .into_iter() .chain(conflicts.iter().flat_map(|Conflict(_, _, fs2)| fs2.clone())) .collect(), )) } fn is_type_conflict(ctx: &ValidatorContext<'a, S>, t1: &Type, t2: &Type) -> bool { match (t1, t2) { (&Type::List(ref inner1, expected_size1), &Type::List(ref inner2, expected_size2)) | ( &Type::NonNullList(ref inner1, expected_size1), &Type::NonNullList(ref inner2, expected_size2), ) => { if expected_size1 != expected_size2 { return false; } Self::is_type_conflict(ctx, inner1, inner2) } (&Type::NonNullNamed(ref n1), &Type::NonNullNamed(ref n2)) | (&Type::Named(ref n1), &Type::Named(ref n2)) => { let ct1 = ctx.schema.concrete_type_by_name(n1); let ct2 = ctx.schema.concrete_type_by_name(n2); if ct1.map(MetaType::is_leaf).unwrap_or(false) || ct2.map(MetaType::is_leaf).unwrap_or(false) { n1 != n2 } else { false } } _ => true, } } fn is_same_arguments( &self, args1: &Option>>, args2: &Option>>, ) -> bool where S: ScalarValue, { match (args1, args2) { (&None, &None) => true, ( &Some(Spanning { item: ref args1, .. }), &Some(Spanning { item: ref args2, .. }), ) => { if args1.len() != args2.len() { return false; } args1.iter().all(|(n1, v1)| { if let Some((_, v2)) = args2.iter().find(|&(n2, _)| n1.item == n2.item) { v1.item.unlocated_eq(&v2.item) } else { false } }) } _ => false, } } fn is_object_type(&self, ctx: &ValidatorContext<'a, S>, type_name: Option<&str>) -> bool { let meta = type_name.and_then(|n| ctx.schema.concrete_type_by_name(n)); matches!(meta, Some(&MetaType::Object(_))) } fn get_referenced_fields_and_fragment_names( &self, fragment: &'a Fragment, ctx: &ValidatorContext<'a, S>, ) -> (AstAndDefCollection<'a, S>, Vec<&'a str>) { let fragment_type = ctx .schema .concrete_type_by_name(fragment.type_condition.item); self.get_fields_and_fragment_names(fragment_type, &fragment.selection_set, ctx) } fn get_fields_and_fragment_names( &self, parent_type: Option<&'a MetaType>, selection_set: &'a [Selection], ctx: &ValidatorContext<'a, S>, ) -> (AstAndDefCollection<'a, S>, Vec<&'a str>) { let mut ast_and_defs = OrderedMap::new(); let mut fragment_names = Vec::new(); Self::collect_fields_and_fragment_names( parent_type, selection_set, ctx, &mut ast_and_defs, &mut fragment_names, ); (ast_and_defs, fragment_names) } fn collect_fields_and_fragment_names( parent_type: Option<&'a MetaType>, selection_set: &'a [Selection], ctx: &ValidatorContext<'a, S>, ast_and_defs: &mut AstAndDefCollection<'a, S>, fragment_names: &mut Vec<&'a str>, ) { for selection in selection_set { match *selection { Selection::Field(ref f) => { let field_name = &f.item.name.item; let field_def = parent_type.and_then(|t| t.field_by_name(field_name)); let response_name = f.item.alias.as_ref().map(|s| &s.item).unwrap_or(field_name); if !ast_and_defs.contains_key(response_name) { ast_and_defs.insert(response_name, Vec::new()); } ast_and_defs.get_mut(response_name).unwrap().push(AstAndDef( parent_type.and_then(MetaType::name), f, field_def, )); } Selection::FragmentSpread(Spanning { item: FragmentSpread { ref name, .. }, .. }) => { if !fragment_names.iter().any(|n| *n == name.item) { fragment_names.push(name.item); } } Selection::InlineFragment(Spanning { item: ref inline, .. }) => { let parent_type = inline .type_condition .as_ref() .and_then(|cond| ctx.schema.concrete_type_by_name(cond.item)) .or(parent_type); Self::collect_fields_and_fragment_names( parent_type, &inline.selection_set, ctx, ast_and_defs, fragment_names, ); } } } } } impl<'a, S> Visitor<'a, S> for OverlappingFieldsCanBeMerged<'a, S> where S: ScalarValue, { fn enter_document(&mut self, _: &mut ValidatorContext<'a, S>, defs: &'a Document) { for def in defs { if let Definition::Fragment(Spanning { ref item, .. }) = *def { self.named_fragments.insert(item.name.item, item); } } } fn enter_selection_set( &mut self, ctx: &mut ValidatorContext<'a, S>, selection_set: &'a [Selection], ) { for Conflict(ConflictReason(reason_name, reason_msg), mut p1, mut p2) in self.find_conflicts_within_selection_set(ctx.parent_type(), selection_set, ctx) { p1.append(&mut p2); ctx.report_error(&error_message(&reason_name, &reason_msg), &p1); } } } fn error_message(reason_name: &str, reason: &ConflictReasonMessage) -> String { let suffix = "Use different aliases on the fields to fetch both if this was intentional"; format!( r#"Fields "{reason_name}" conflict because {}. {suffix}"#, format_reason(reason), ) } fn format_reason(reason: &ConflictReasonMessage) -> String { match reason { ConflictReasonMessage::Message(name) => name.clone(), ConflictReasonMessage::Nested(nested) => nested .iter() .map(|ConflictReason(name, subreason)| { format!( r#"subfields "{name}" conflict because {}"#, format_reason(subreason), ) }) .collect::>() .join(" and "), } } #[cfg(test)] mod tests { use super::{error_message, factory, ConflictReason, ConflictReasonMessage::*}; use crate::{ executor::Registry, schema::meta::MetaType, types::{ base::{GraphQLType, GraphQLValue}, scalars::{EmptyMutation, EmptySubscription, ID}, }, }; use crate::{ parser::SourcePosition, validation::{ expect_fails_rule, expect_fails_rule_with_schema, expect_passes_rule, expect_passes_rule_with_schema, RuleError, }, value::{DefaultScalarValue, ScalarValue}, }; #[test] fn unique_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment uniqueFields on Dog { name nickname } "#, ); } #[test] fn identical_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment mergeIdenticalFields on Dog { name name } "#, ); } #[test] fn identical_fields_with_identical_args() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment mergeIdenticalFieldsWithIdenticalArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: SIT) } "#, ); } #[test] fn identical_fields_with_identical_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment mergeSameFieldsWithSameDirectives on Dog { name @include(if: true) name @include(if: true) } "#, ); } #[test] fn different_args_with_different_aliases() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment differentArgsWithDifferentAliases on Dog { knowsSit: doesKnowCommand(dogCommand: SIT) knowsDown: doesKnowCommand(dogCommand: DOWN) } "#, ); } #[test] fn different_directives_with_different_aliases() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment differentDirectivesWithDifferentAliases on Dog { nameIfTrue: name @include(if: true) nameIfFalse: name @include(if: false) } "#, ); } #[test] fn different_skip_include_directives_accepted() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment differentDirectivesWithDifferentAliases on Dog { name @include(if: true) name @include(if: false) } "#, ); } #[test] fn same_aliases_with_different_field_targets() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment sameAliasesWithDifferentFieldTargets on Dog { fido: name fido: nickname } "#, &[RuleError::new( &error_message( "fido", &Message("name and nickname are different fields".into()), ), &[ SourcePosition::new(78, 2, 12), SourcePosition::new(101, 3, 12), ], )], ); } #[test] fn same_aliases_allowed_on_nonoverlapping_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment sameAliasesWithDifferentFieldTargets on Pet { ... on Dog { name } ... on Cat { name: nickname } } "#, ); } #[test] fn alias_masking_direct_field_access() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment aliasMaskingDirectFieldAccess on Dog { name: nickname name } "#, &[RuleError::new( &error_message( "name", &Message("nickname and name are different fields".into()), ), &[ SourcePosition::new(71, 2, 12), SourcePosition::new(98, 3, 12), ], )], ); } #[test] fn different_args_second_adds_an_argument() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment conflictingArgs on Dog { doesKnowCommand doesKnowCommand(dogCommand: HEEL) } "#, &[RuleError::new( &error_message( "doesKnowCommand", &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), SourcePosition::new(85, 3, 12), ], )], ); } #[test] fn different_args_second_missing_an_argument() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment conflictingArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand } "#, &[RuleError::new( &error_message( "doesKnowCommand", &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), SourcePosition::new(102, 3, 12), ], )], ); } #[test] fn conflicting_args() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment conflictingArgs on Dog { doesKnowCommand(dogCommand: SIT) doesKnowCommand(dogCommand: HEEL) } "#, &[RuleError::new( &error_message( "doesKnowCommand", &Message("they have differing arguments".into()), ), &[ SourcePosition::new(57, 2, 12), SourcePosition::new(102, 3, 12), ], )], ); } #[test] fn allows_different_args_where_no_conflict_is_possible() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment conflictingArgs on Pet { ... on Dog { name(surname: true) } ... on Cat { name } } "#, ); } #[test] fn encounters_conflict_in_fragments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { ...A ...B } fragment A on Dog { x: name } fragment B on Dog { x: barks } "#, &[RuleError::new( &error_message("x", &Message("name and barks are different fields".into())), &[ SourcePosition::new(101, 6, 12), SourcePosition::new(163, 9, 12), ], )], ); } #[test] fn reports_each_conflict_once() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dorOrHuman { ...A ...B } catOrDog { ...B ...A } dog { ...A ...B x: name } } fragment A on Dog { x: barks } fragment B on Dog { x: nickname } "#, &[ RuleError::new( &error_message("x", &Message("name and barks are different fields".into())), &[ SourcePosition::new(235, 13, 14), SourcePosition::new(311, 17, 12), ], ), RuleError::new( &error_message( "x", &Message("name and nickname are different fields".into()), ), &[ SourcePosition::new(235, 13, 14), SourcePosition::new(374, 20, 12), ], ), RuleError::new( &error_message( "x", &Message("barks and nickname are different fields".into()), ), &[ SourcePosition::new(311, 17, 12), SourcePosition::new(374, 20, 12), ], ), ], ); } #[test] fn deep_conflict() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { x: name }, dog { x: barks } } "#, &[RuleError::new( &error_message( "dog", &Nested(vec![ConflictReason( "x".into(), Message("name and barks are different fields".into()), )]), ), &[ SourcePosition::new(25, 2, 12), SourcePosition::new(45, 3, 14), SourcePosition::new(80, 5, 12), SourcePosition::new(100, 6, 14), ], )], ); } #[test] fn deep_conflict_with_multiple_issues() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { x: barks y: name }, dog { x: nickname y: barkVolume } } "#, &[RuleError::new( &error_message( "dog", &Nested(vec![ ConflictReason( "x".into(), Message("barks and nickname are different fields".into()), ), ConflictReason( "y".into(), Message("name and barkVolume are different fields".into()), ), ]), ), &[ SourcePosition::new(25, 2, 12), SourcePosition::new(45, 3, 14), SourcePosition::new(68, 4, 14), SourcePosition::new(105, 6, 14), SourcePosition::new(125, 7, 14), SourcePosition::new(151, 8, 14), ], )], ); } #[test] fn very_deep_conflict() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { human { relatives { x: name } }, human { relatives { x: iq } } } "#, &[RuleError::new( &error_message( "human", &Nested(vec![ConflictReason( "relatives".into(), Nested(vec![ConflictReason( "x".into(), Message("name and iq are different fields".into()), )]), )]), ), &[ SourcePosition::new(25, 2, 12), SourcePosition::new(47, 3, 14), SourcePosition::new(75, 4, 16), SourcePosition::new(126, 7, 12), SourcePosition::new(148, 8, 14), SourcePosition::new(176, 9, 16), ], )], ); } #[test] fn reports_deep_conflict_to_nearest_common_ancestor() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { human { relatives { x: iq } relatives { x: name } }, human { relatives { iq } } } "#, &[RuleError::new( &error_message( "relatives", &Nested(vec![ConflictReason( "x".into(), Message("iq and name are different fields".into()), )]), ), &[ SourcePosition::new(47, 3, 14), SourcePosition::new(75, 4, 16), SourcePosition::new(111, 6, 14), SourcePosition::new(139, 7, 16), ], )], ); } #[test] fn reports_deep_conflict_to_nearest_common_ancestor_in_fragments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { human { ...F } human { ...F } } fragment F on Human { relatives { relatives { x: iq } relatives { x: name } }, relatives { relatives { iq } } } "#, &[RuleError::new( &error_message( "relatives", &Nested(vec![ConflictReason( "x".into(), Message("iq and name are different fields".into()), )]), ), &[ SourcePosition::new(201, 11, 14), SourcePosition::new(229, 12, 16), SourcePosition::new(265, 14, 14), SourcePosition::new(293, 15, 16), ], )], ); } #[test] fn reports_deep_conflict_in_nested_fragments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { ...F } dog { ...I } } fragment F on Dog { x: name ...G } fragment G on Dog { y: barkVolume } fragment I on Dog { y: nickname ...J } fragment J on Dog { x: barks } "#, &[RuleError::new( &error_message( "dog", &Nested(vec![ ConflictReason( "x".into(), Message("name and barks are different fields".into()), ), ConflictReason( "y".into(), Message("barkVolume and nickname are different fields".into()), ), ]), ), &[ SourcePosition::new(25, 2, 12), SourcePosition::new(169, 10, 12), SourcePosition::new(248, 14, 12), SourcePosition::new(76, 5, 12), SourcePosition::new(399, 21, 12), SourcePosition::new(316, 17, 12), ], )], ); } #[test] fn ignores_unknown_fragments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { name } ...Unknown ...Known } fragment Known on QueryRoot { dog { name } ...OtherUnknown } "#, ); } struct SomeBox; struct StringBox; struct IntBox; struct NonNullStringBox1; struct NonNullStringBox1Impl; struct NonNullStringBox2; struct NonNullStringBox2Impl; struct Connection; struct Edge; struct Node; struct QueryRoot; impl GraphQLType for SomeBox where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("SomeBox") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::>("deepBox", i), registry.field::>("unrelatedField", i), registry.field::>("otherField", i), ]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for SomeBox where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for StringBox where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("StringBox") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::>("scalar", i), registry.field::>("deepBox", i), registry.field::>("unrelatedField", i), registry.field::>>>("listStringBox", i), registry.field::>("stringBox", i), registry.field::>("intBox", i), ]; registry .build_object_type::(i, fields) .interfaces(&[registry.get_type::(i)]) .into_meta() } } impl GraphQLValue for StringBox where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for IntBox where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("IntBox") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::>("scalar", i), registry.field::>("deepBox", i), registry.field::>("unrelatedField", i), registry.field::>>>("listStringBox", i), registry.field::>("stringBox", i), registry.field::>("intBox", i), ]; registry .build_object_type::(i, fields) .interfaces(&[registry.get_type::(i)]) .into_meta() } } impl GraphQLValue for IntBox where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for NonNullStringBox1 where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry.field::("scalar", i)]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for NonNullStringBox1 where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for NonNullStringBox1Impl where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1Impl") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::("scalar", i), registry.field::>("deepBox", i), registry.field::>("unrelatedField", i), ]; registry .build_object_type::(i, fields) .interfaces(&[ registry.get_type::(i), registry.get_type::(i), ]) .into_meta() } } impl GraphQLValue for NonNullStringBox1Impl where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for NonNullStringBox2 where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry.field::("scalar", i)]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for NonNullStringBox2 where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for NonNullStringBox2Impl where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2Impl") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::("scalar", i), registry.field::>("deepBox", i), registry.field::>("unrelatedField", i), ]; registry .build_object_type::(i, fields) .interfaces(&[ registry.get_type::(i), registry.get_type::(i), ]) .into_meta() } } impl GraphQLValue for NonNullStringBox2Impl where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Node where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Node") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.field::>("id", i), registry.field::>("name", i), ]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for Node where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Edge where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Edge") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry.field::>("node", i)]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for Edge where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Connection where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Connection") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry.field::>>>("edges", i)]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for Connection where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for QueryRoot where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry.get_type::(i); registry.get_type::(i); registry.get_type::(i); registry.get_type::(i); let fields = &[ registry.field::>("someBox", i), registry.field::>("connection", i), ]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for QueryRoot where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } #[test] fn conflicting_return_types_which_potentially_overlap() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ...on IntBox { scalar } ...on NonNullStringBox1 { scalar } } } "#, &[RuleError::new( &error_message( "scalar", &Message("they return conflicting types Int and String!".into()), ), &[ SourcePosition::new(88, 4, 18), SourcePosition::new(173, 7, 18), ], )], ); } #[test] fn compatible_return_shapes_on_different_return_types() { expect_passes_rule_with_schema::< _, EmptyMutation<()>, EmptySubscription<()>, _, _, DefaultScalarValue, >( QueryRoot, EmptyMutation::new(), EmptySubscription::new(), factory, r#" { someBox { ... on SomeBox { deepBox { unrelatedField } } ... on StringBox { deepBox { unrelatedField } } } } "#, ); } #[test] fn disallows_differing_return_types_despite_no_overlap() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { scalar } ... on StringBox { scalar } } } "#, &[RuleError::new( &error_message( "scalar", &Message("they return conflicting types Int and String".into()), ), &[ SourcePosition::new(89, 4, 18), SourcePosition::new(167, 7, 18), ], )], ); } #[test] fn reports_correctly_when_a_non_exclusive_follows_an_exclusive() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { deepBox { ...X } } } someBox { ... on StringBox { deepBox { ...Y } } } memoed: someBox { ... on IntBox { deepBox { ...X } } } memoed: someBox { ... on StringBox { deepBox { ...Y } } } other: someBox { ...X } other: someBox { ...Y } } fragment X on SomeBox { otherField } fragment Y on SomeBox { otherField: unrelatedField } "#, &[RuleError::new( &error_message( "other", &Nested(vec![ConflictReason( "otherField".into(), Message("otherField and unrelatedField are different fields".into()), )]), ), &[ SourcePosition::new(703, 30, 14), SourcePosition::new(889, 38, 14), SourcePosition::new(771, 33, 14), SourcePosition::new(964, 41, 14), ], )], ); } #[test] fn disallows_differing_return_type_nullability_despite_no_overlap() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on NonNullStringBox1 { scalar } ... on StringBox { scalar } } } "#, &[RuleError::new( &error_message( "scalar", &Message("they return conflicting types String! and String".into()), ), &[ SourcePosition::new(100, 4, 18), SourcePosition::new(178, 7, 18), ], )], ); } #[test] fn disallows_differing_return_type_list_despite_no_overlap() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { box: listStringBox { scalar } } ... on StringBox { box: stringBox { scalar } } } } "#, &[RuleError::new( &error_message( "box", &Message("they return conflicting types [StringBox] and StringBox".into()), ), &[ SourcePosition::new(89, 4, 18), SourcePosition::new(228, 9, 18), ], )], ); expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: listStringBox { scalar } } } } "#, &[RuleError::new( &error_message( "box", &Message("they return conflicting types StringBox and [StringBox]".into()), ), &[ SourcePosition::new(89, 4, 18), SourcePosition::new(224, 9, 18), ], )], ); } #[test] fn disallows_differing_subfields() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { box: stringBox { val: scalar val: unrelatedField } } ... on StringBox { box: stringBox { val: scalar } } } } "#, &[RuleError::new( &error_message( "val", &Message("scalar and unrelatedField are different fields".into()), ), &[ SourcePosition::new(126, 5, 20), SourcePosition::new(158, 6, 20), ], )], ); } #[test] fn disallows_differing_deep_return_types_despite_no_overlap() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: intBox { scalar } } } } "#, &[RuleError::new( &error_message( "box", &Nested(vec![ConflictReason( "scalar".into(), Message("they return conflicting types String and Int".into()), )]), ), &[ SourcePosition::new(89, 4, 18), SourcePosition::new(126, 5, 20), SourcePosition::new(224, 9, 18), SourcePosition::new(258, 10, 20), ], )], ); } #[test] fn allows_non_conflicting_overlapping_types() { expect_passes_rule_with_schema::< _, EmptyMutation<()>, EmptySubscription<()>, _, _, DefaultScalarValue, >( QueryRoot, EmptyMutation::new(), EmptySubscription::new(), factory, r#" { someBox { ... on IntBox { scalar: unrelatedField } ... on StringBox { scalar } } } "#, ); } #[test] fn same_wrapped_scalar_return_types() { expect_passes_rule_with_schema::< _, EmptyMutation<()>, EmptySubscription<()>, _, _, DefaultScalarValue, >( QueryRoot, EmptyMutation::new(), EmptySubscription::new(), factory, r#" { someBox { ...on NonNullStringBox1 { scalar } ...on NonNullStringBox2 { scalar } } } "#, ); } #[test] fn allows_inline_typeless_fragments() { expect_passes_rule_with_schema::< _, EmptyMutation<()>, EmptySubscription<()>, _, _, DefaultScalarValue, >( QueryRoot, EmptyMutation::new(), EmptySubscription::new(), factory, r#" { someBox { unrelatedField } ... { someBox { unrelatedField } } } "#, ); } #[test] fn compares_deep_types_including_list() { expect_fails_rule_with_schema::<_, EmptyMutation<()>, _, _, DefaultScalarValue>( QueryRoot, EmptyMutation::new(), factory, r#" { connection { ...edgeID edges { node { id: name } } } } fragment edgeID on Connection { edges { node { id } } } "#, &[RuleError::new( &error_message( "edges", &Nested(vec![ConflictReason( "node".into(), Nested(vec![ConflictReason( "id".into(), Message("name and id are different fields".into()), )]), )]), ), &[ SourcePosition::new(84, 4, 16), SourcePosition::new(110, 5, 18), SourcePosition::new(137, 6, 20), SourcePosition::new(273, 13, 14), SourcePosition::new(297, 14, 16), SourcePosition::new(322, 15, 18), ], )], ); } #[test] fn ignores_unknown_types() { expect_passes_rule_with_schema::< _, EmptyMutation<()>, EmptySubscription<()>, _, _, DefaultScalarValue, >( QueryRoot, EmptyMutation::new(), EmptySubscription::new(), factory, r#" { someBox { ...on UnknownType { scalar } ...on NonNullStringBox2 { scalar } } } "#, ); } #[test] fn error_message_contains_hint_for_alias_conflict() { assert_eq!( &error_message("x", &Message("a and b are different fields".into())), "Fields \"x\" conflict because a and b are different fields. Use \ different aliases on the fields to fetch both if this \ was intentional" ); } } juniper-0.16.2/src/validation/rules/possible_fragment_spreads.rs000064400000000000000000000332241046102023000232370ustar 00000000000000use std::fmt::Debug; use crate::{ ast::{Definition, Document, FragmentSpread, InlineFragment}, meta::InterfaceMeta, parser::Spanning, schema::meta::MetaType, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; use std::collections::HashMap; pub struct PossibleFragmentSpreads<'a, S: Debug + 'a> { fragment_types: HashMap<&'a str, &'a MetaType<'a, S>>, } pub fn factory<'a, S: Debug>() -> PossibleFragmentSpreads<'a, S> { PossibleFragmentSpreads { fragment_types: HashMap::new(), } } impl<'a, S> Visitor<'a, S> for PossibleFragmentSpreads<'a, S> where S: ScalarValue, { fn enter_document(&mut self, ctx: &mut ValidatorContext<'a, S>, defs: &'a Document) { for def in defs { if let Definition::Fragment(Spanning { ref item, .. }) = *def { if let Some(t) = ctx.schema.concrete_type_by_name(item.type_condition.item) { self.fragment_types.insert(item.name.item, t); } } } } fn enter_inline_fragment( &mut self, ctx: &mut ValidatorContext<'a, S>, frag: &'a Spanning>, ) { if let (Some(parent_type), Some(frag_type)) = ( ctx.parent_type(), frag.item .type_condition .as_ref() .and_then(|s| ctx.schema.concrete_type_by_name(s.item)), ) { // Even if there is no object type in the overlap of interfaces // implementers, it's OK to spread in case `frag_type` implements // `parent_type`. // https://spec.graphql.org/October2021#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { interface_names, .. }) = frag_type { let implements_parent = parent_type .name() .map(|parent| interface_names.iter().any(|i| i == parent)) .unwrap_or_default(); if implements_parent { return; } } if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( None, parent_type.name().unwrap_or(""), frag_type.name().unwrap_or(""), ), &[frag.span.start], ); } } } fn enter_fragment_spread( &mut self, ctx: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let (Some(parent_type), Some(frag_type)) = ( ctx.parent_type(), self.fragment_types.get(spread.item.name.item), ) { // Even if there is no object type in the overlap of interfaces // implementers, it's OK to spread in case `frag_type` implements // `parent_type`. // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { interface_names, .. }) = frag_type { let implements_parent = parent_type .name() .map(|parent| interface_names.iter().any(|i| i == parent)) .unwrap_or_default(); if implements_parent { return; } } if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( Some(spread.item.name.item), parent_type.name().unwrap_or(""), frag_type.name().unwrap_or(""), ), &[spread.span.start], ); } } } } fn error_message(frag_name: Option<&str>, parent_type_name: &str, frag_type: &str) -> String { if let Some(frag_name) = frag_name { format!( "Fragment \"{frag_name}\" cannot be spread here as objects of type \ \"{parent_type_name}\" can never be of type \"{frag_type}\"", ) } else { format!( "Fragment cannot be spread here as objects of type \ \"{parent_type_name}\" can never be of type \"{frag_type}\"", ) } } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn of_the_same_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectWithinObject on Dog { ...dogFragment } fragment dogFragment on Dog { barkVolume } "#, ); } #[test] fn of_the_same_object_with_inline_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } } "#, ); } #[test] fn object_into_an_implemented_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectWithinInterface on Pet { ...dogFragment } fragment dogFragment on Dog { barkVolume } "#, ); } #[test] fn object_into_containing_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment objectWithinUnion on CatOrDog { ...dogFragment } fragment dogFragment on Dog { barkVolume } "#, ); } #[test] fn union_into_contained_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment unionWithinObject on Dog { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } "#, ); } #[test] fn union_into_overlapping_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment unionWithinInterface on Pet { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } "#, ); } #[test] fn union_into_overlapping_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } "#, ); } #[test] fn interface_into_implemented_object() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceWithinObject on Dog { ...petFragment } fragment petFragment on Pet { name } "#, ); } #[test] fn interface_into_overlapping_interface() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceWithinInterface on Pet { ...beingFragment } fragment beingFragment on Being { name } "#, ); } #[test] fn interface_into_overlapping_interface_in_inline_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceWithinInterface on Pet { ... on Being { name } } "#, ); } #[test] fn interface_into_overlapping_union() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment interfaceWithinUnion on CatOrDog { ...petFragment } fragment petFragment on Pet { name } "#, ); } #[test] fn no_object_overlap_but_implements_parent() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment beingFragment on Being { ...unpopulatedFragment } fragment unpopulatedFragment on Unpopulated { name } "#, ); } #[test] fn no_object_overlap_but_implements_parent_inline() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment beingFragment on Being { ...on Unpopulated { name } } "#, ); } #[test] fn different_object_into_object() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidObjectWithinObject on Cat { ...dogFragment } fragment dogFragment on Dog { barkVolume } "#, &[RuleError::new( &error_message(Some("dogFragment"), "Cat", "Dog"), &[SourcePosition::new(55, 1, 54)], )], ); } #[test] fn different_object_into_object_in_inline_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidObjectWithinObjectAnon on Cat { ... on Dog { barkVolume } } "#, &[RuleError::new( &error_message(None, "Cat", "Dog"), &[SourcePosition::new(71, 2, 12)], )], ); } #[test] fn object_into_not_implementing_interface() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidObjectWithinInterface on Pet { ...humanFragment } fragment humanFragment on Human { pets { name } } "#, &[RuleError::new( &error_message(Some("humanFragment"), "Pet", "Human"), &[SourcePosition::new(58, 1, 57)], )], ); } #[test] fn object_into_not_containing_union() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment } fragment humanFragment on Human { pets { name } } "#, &[RuleError::new( &error_message(Some("humanFragment"), "CatOrDog", "Human"), &[SourcePosition::new(59, 1, 58)], )], ); } #[test] fn union_into_not_contained_object() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidUnionWithinObject on Human { ...catOrDogFragment } fragment catOrDogFragment on CatOrDog { __typename } "#, &[RuleError::new( &error_message(Some("catOrDogFragment"), "Human", "CatOrDog"), &[SourcePosition::new(56, 1, 55)], )], ); } #[test] fn union_into_non_overlapping_interface() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment } fragment humanOrAlienFragment on HumanOrAlien { __typename } "#, &[RuleError::new( &error_message(Some("humanOrAlienFragment"), "Pet", "HumanOrAlien"), &[SourcePosition::new(57, 1, 56)], )], ); } #[test] fn union_into_non_overlapping_union() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment } fragment humanOrAlienFragment on HumanOrAlien { __typename } "#, &[RuleError::new( &error_message(Some("humanOrAlienFragment"), "CatOrDog", "HumanOrAlien"), &[SourcePosition::new(58, 1, 57)], )], ); } #[test] fn interface_into_non_implementing_object() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment } fragment intelligentFragment on Intelligent { iq } "#, &[RuleError::new( &error_message(Some("intelligentFragment"), "Cat", "Intelligent"), &[SourcePosition::new(58, 1, 57)], )], ); } #[test] fn interface_into_non_overlapping_interface() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidInterfaceWithinInterface on Pet { ...intelligentFragment } fragment intelligentFragment on Intelligent { iq } "#, &[RuleError::new( &error_message(Some("intelligentFragment"), "Pet", "Intelligent"), &[SourcePosition::new(73, 2, 12)], )], ); } #[test] fn interface_into_non_overlapping_interface_in_inline_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidInterfaceWithinInterfaceAnon on Pet { ...on Intelligent { iq } } "#, &[RuleError::new( &error_message(None, "Pet", "Intelligent"), &[SourcePosition::new(77, 2, 12)], )], ); } #[test] fn interface_into_non_overlapping_union() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment } fragment petFragment on Pet { name } "#, &[RuleError::new( &error_message(Some("petFragment"), "HumanOrAlien", "Pet"), &[SourcePosition::new(66, 1, 65)], )], ); } } juniper-0.16.2/src/validation/rules/provided_non_null_arguments.rs000064400000000000000000000222101046102023000236110ustar 00000000000000use std::fmt; use crate::{ ast::{Directive, Field}, parser::Spanning, schema::{meta::Field as FieldType, model::DirectiveType}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct ProvidedNonNullArguments; pub fn factory() -> ProvidedNonNullArguments { ProvidedNonNullArguments } impl<'a, S> Visitor<'a, S> for ProvidedNonNullArguments where S: ScalarValue, { fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>) { let field_name = &field.item.name.item; if let Some(&FieldType { arguments: Some(ref meta_args), .. }) = ctx.parent_type().and_then(|t| t.field_by_name(field_name)) { for meta_arg in meta_args { if meta_arg.arg_type.is_non_null() && meta_arg.default_value.is_none() && field .item .arguments .as_ref() .and_then(|args| args.item.get(&meta_arg.name)) .is_none() { ctx.report_error( &field_error_message(field_name, &meta_arg.name, &meta_arg.arg_type), &[field.span.start], ); } } } } fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, directive: &'a Spanning>, ) { let directive_name = &directive.item.name.item; if let Some(DirectiveType { arguments: meta_args, .. }) = ctx.schema.directive_by_name(directive_name) { for meta_arg in meta_args { if meta_arg.arg_type.is_non_null() && directive .item .arguments .as_ref() .and_then(|args| args.item.get(&meta_arg.name)) .is_none() { ctx.report_error( &directive_error_message( directive_name, &meta_arg.name, &meta_arg.arg_type, ), &[directive.span.start], ); } } } } } fn field_error_message( field_name: impl fmt::Display, arg_name: impl fmt::Display, type_name: impl fmt::Display, ) -> String { format!( r#"Field "{field_name}" argument "{arg_name}" of type "{type_name}" is required but not provided"#, ) } fn directive_error_message( directive_name: impl fmt::Display, arg_name: impl fmt::Display, type_name: impl fmt::Display, ) -> String { format!( r#"Directive "@{directive_name}" argument "{arg_name}" of type "{type_name}" is required but not provided"#, ) } #[cfg(test)] mod tests { use super::{directive_error_message, factory, field_error_message}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn ignores_unknown_arguments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { isHousetrained(unknownArgument: true) } } "#, ); } #[test] fn arg_on_optional_arg() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { isHousetrained(atOtherHomes: true) } } "#, ); } #[test] fn no_arg_on_optional_arg() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { isHousetrained } } "#, ); } #[test] fn multiple_args() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req1: 1, req2: 2) } } "#, ); } #[test] fn multiple_args_reverse_order() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req2: 2, req1: 1) } } "#, ); } #[test] fn no_args_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts } } "#, ); } #[test] fn one_arg_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts(opt1: 1) } } "#, ); } #[test] fn second_arg_on_multiple_optional() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOpts(opt2: 1) } } "#, ); } #[test] fn muliple_reqs_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4) } } "#, ); } #[test] fn multiple_reqs_and_one_opt_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5) } } "#, ); } #[test] fn all_reqs_on_opts_on_mixed_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) } } "#, ); } #[test] fn missing_one_non_nullable_argument() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req2: 2) } } "#, &[RuleError::new( &field_error_message("multipleReqs", "req1", "Int!"), &[SourcePosition::new(63, 3, 16)], )], ); } #[test] fn missing_multiple_non_nullable_arguments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs } } "#, &[ RuleError::new( &field_error_message("multipleReqs", "req1", "Int!"), &[SourcePosition::new(63, 3, 16)], ), RuleError::new( &field_error_message("multipleReqs", "req2", "Int!"), &[SourcePosition::new(63, 3, 16)], ), ], ); } #[test] fn incorrect_value_and_missing_argument() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { complicatedArgs { multipleReqs(req1: "one") } } "#, &[RuleError::new( &field_error_message("multipleReqs", "req2", "Int!"), &[SourcePosition::new(63, 3, 16)], )], ); } #[test] fn ignores_unknown_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @unknown } "#, ); } #[test] fn with_directives_of_valid_types() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @include(if: true) { name } human @skip(if: false) { name } } "#, ); } #[test] fn with_directive_with_missing_types() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @include { name @skip } } "#, &[ RuleError::new( &directive_error_message("include", "if", "Boolean!"), &[SourcePosition::new(33, 2, 18)], ), RuleError::new( &directive_error_message("skip", "if", "Boolean!"), &[SourcePosition::new(65, 3, 21)], ), ], ); } } juniper-0.16.2/src/validation/rules/scalar_leafs.rs000064400000000000000000000131361046102023000204320ustar 00000000000000use std::fmt; use crate::{ ast::Field, parser::Spanning, validation::{RuleError, ValidatorContext, Visitor}, value::ScalarValue, }; pub struct ScalarLeafs; pub fn factory() -> ScalarLeafs { ScalarLeafs } impl<'a, S> Visitor<'a, S> for ScalarLeafs where S: ScalarValue, { fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>) { let field_name = &field.item.name.item; let error = if let (Some(field_type), Some(field_type_literal)) = (ctx.current_type(), ctx.current_type_literal()) { match (field_type.is_leaf(), &field.item.selection_set) { (true, &Some(_)) => Some(RuleError::new( &no_allowed_error_message(field_name, field_type_literal), &[field.span.start], )), (false, &None) => Some(RuleError::new( &required_error_message(field_name, field_type_literal), &[field.span.start], )), _ => None, } } else { None }; if let Some(error) = error { ctx.append_errors(vec![error]); } } } fn no_allowed_error_message(field_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( r#"Field "{field_name}" must not have a selection since type {type_name} has no subfields"#, ) } fn required_error_message(field_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!( r#"Field "{field_name}" of type "{type_name}" must have a selection of subfields. Did you mean "{field_name} {{ ... }}"?"#, ) } #[cfg(test)] mod tests { use super::{factory, no_allowed_error_message, required_error_message}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn valid_scalar_selection() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelection on Dog { barks } "#, ); } #[test] fn object_type_missing_selection() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query directQueryOnObjectWithoutSubFields { human } "#, &[RuleError::new( &required_error_message("human", "Human"), &[SourcePosition::new(67, 2, 12)], )], ); } #[test] fn interface_type_missing_selection() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { human { pets } } "#, &[RuleError::new( &required_error_message("pets", "[Pet]"), &[SourcePosition::new(33, 2, 20)], )], ); } #[test] fn valid_scalar_selection_with_args() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionWithArgs on Dog { doesKnowCommand(dogCommand: SIT) } "#, ); } #[test] fn scalar_selection_not_allowed_on_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionsNotAllowedOnBoolean on Dog { barks { sinceWhen } } "#, &[RuleError::new( &no_allowed_error_message("barks", "Boolean"), &[SourcePosition::new(77, 2, 12)], )], ); } #[test] fn scalar_selection_not_allowed_on_enum() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionsNotAllowedOnEnum on Cat { furColor { inHexdec } } "#, &[RuleError::new( &no_allowed_error_message("furColor", "FurColor"), &[SourcePosition::new(74, 2, 12)], )], ); } #[test] fn scalar_selection_not_allowed_with_args() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionsNotAllowedWithArgs on Dog { doesKnowCommand(dogCommand: SIT) { sinceWhen } } "#, &[RuleError::new( &no_allowed_error_message("doesKnowCommand", "Boolean"), &[SourcePosition::new(76, 2, 12)], )], ); } #[test] fn scalar_selection_not_allowed_with_directives() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionsNotAllowedWithDirectives on Dog { name @include(if: true) { isAlsoHumanName } } "#, &[RuleError::new( &no_allowed_error_message("name", "String"), &[SourcePosition::new(82, 2, 12)], )], ); } #[test] fn scalar_selection_not_allowed_with_directives_and_args() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } } "#, &[RuleError::new( &no_allowed_error_message("doesKnowCommand", "Boolean"), &[SourcePosition::new(89, 2, 12)], )], ); } } juniper-0.16.2/src/validation/rules/unique_argument_names.rs000064400000000000000000000145321046102023000224070ustar 00000000000000use std::collections::hash_map::{Entry, HashMap}; use crate::{ ast::{Directive, Field, InputValue}, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct UniqueArgumentNames<'a> { known_names: HashMap<&'a str, SourcePosition>, } pub fn factory<'a>() -> UniqueArgumentNames<'a> { UniqueArgumentNames { known_names: HashMap::new(), } } impl<'a, S> Visitor<'a, S> for UniqueArgumentNames<'a> where S: ScalarValue, { fn enter_directive(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.known_names = HashMap::new(); } fn enter_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { self.known_names = HashMap::new(); } fn enter_argument( &mut self, ctx: &mut ValidatorContext<'a, S>, (arg_name, _): &'a (Spanning<&'a str>, Spanning>), ) { match self.known_names.entry(arg_name.item) { Entry::Occupied(e) => { ctx.report_error( &error_message(arg_name.item), &[*e.get(), arg_name.span.start], ); } Entry::Vacant(e) => { e.insert(arg_name.span.start); } } } } fn error_message(arg_name: &str) -> String { format!("There can only be one argument named \"{arg_name}\"") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn no_arguments_on_field() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field } "#, ); } #[test] fn no_arguments_on_directive() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @directive } "#, ); } #[test] fn argument_on_field() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: "value") } "#, ); } #[test] fn argument_on_directive() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog @directive(arg: "value") } "#, ); } #[test] fn same_argument_on_two_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { one: field(arg: "value") two: field(arg: "value") } "#, ); } #[test] fn same_argument_on_field_and_directive() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: "value") @directive(arg: "value") } "#, ); } #[test] fn same_argument_on_two_directives() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field @directive1(arg: "value") @directive2(arg: "value") } "#, ); } #[test] fn multiple_field_arguments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg1: "value", arg2: "value", arg3: "value") } "#, ); } #[test] fn multiple_directive_arguments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field @directive(arg1: "value", arg2: "value", arg3: "value") } "#, ); } #[test] fn duplicate_field_arguments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg1: "value", arg1: "value") } "#, &[RuleError::new( &error_message("arg1"), &[ SourcePosition::new(31, 2, 18), SourcePosition::new(46, 2, 33), ], )], ); } #[test] fn many_duplicate_field_arguments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg1: "value", arg1: "value", arg1: "value") } "#, &[ RuleError::new( &error_message("arg1"), &[ SourcePosition::new(31, 2, 18), SourcePosition::new(46, 2, 33), ], ), RuleError::new( &error_message("arg1"), &[ SourcePosition::new(31, 2, 18), SourcePosition::new(61, 2, 48), ], ), ], ); } #[test] fn duplicate_directive_arguments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field @directive(arg1: "value", arg1: "value") } "#, &[RuleError::new( &error_message("arg1"), &[ SourcePosition::new(42, 2, 29), SourcePosition::new(57, 2, 44), ], )], ); } #[test] fn many_duplicate_directive_arguments() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field @directive(arg1: "value", arg1: "value", arg1: "value") } "#, &[ RuleError::new( &error_message("arg1"), &[ SourcePosition::new(42, 2, 29), SourcePosition::new(57, 2, 44), ], ), RuleError::new( &error_message("arg1"), &[ SourcePosition::new(42, 2, 29), SourcePosition::new(72, 2, 59), ], ), ], ); } } juniper-0.16.2/src/validation/rules/unique_fragment_names.rs000064400000000000000000000102411046102023000223610ustar 00000000000000use std::collections::hash_map::{Entry, HashMap}; use crate::{ ast::Fragment, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct UniqueFragmentNames<'a> { names: HashMap<&'a str, SourcePosition>, } pub fn factory<'a>() -> UniqueFragmentNames<'a> { UniqueFragmentNames { names: HashMap::new(), } } impl<'a, S> Visitor<'a, S> for UniqueFragmentNames<'a> where S: ScalarValue, { fn enter_fragment_definition( &mut self, context: &mut ValidatorContext<'a, S>, f: &'a Spanning>, ) { match self.names.entry(f.item.name.item) { Entry::Occupied(e) => { context.report_error( &duplicate_message(f.item.name.item), &[*e.get(), f.item.name.span.start], ); } Entry::Vacant(e) => { e.insert(f.item.name.span.start); } } } } fn duplicate_message(frag_name: &str) -> String { format!("There can only be one fragment named {frag_name}") } #[cfg(test)] mod tests { use super::{duplicate_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn no_fragments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { name } } "#, ); } #[test] fn one_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { ...fragA } } fragment fragA on Dog { name } "#, ); } #[test] fn many_fragments() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { ...fragA ...fragB ...fragC } } fragment fragA on Dog { name } fragment fragB on Dog { nickname } fragment fragC on Dog { barkVolume } "#, ); } #[test] fn inline_fragments_always_unique() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { dorOrHuman { ...on Dog { name } ...on Dog { barkVolume } } } "#, ); } #[test] fn fragment_and_operation_named_the_same() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { dog { ...Foo } } fragment Foo on Dog { name } "#, ); } #[test] fn fragments_named_the_same() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { dog { ...fragA } } fragment fragA on Dog { name } fragment fragA on Dog { barkVolume } "#, &[RuleError::new( &duplicate_message("fragA"), &[ SourcePosition::new(99, 6, 19), SourcePosition::new(162, 9, 19), ], )], ); } #[test] fn fragments_named_the_same_no_reference() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { name } fragment fragA on Dog { barkVolume } "#, &[RuleError::new( &duplicate_message("fragA"), &[ SourcePosition::new(20, 1, 19), SourcePosition::new(83, 4, 19), ], )], ); } } juniper-0.16.2/src/validation/rules/unique_input_field_names.rs000064400000000000000000000105521046102023000230650ustar 00000000000000use std::collections::hash_map::{Entry, HashMap}; use crate::{ ast::InputValue, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, Span, }; pub struct UniqueInputFieldNames<'a> { known_name_stack: Vec>, } pub fn factory<'a>() -> UniqueInputFieldNames<'a> { UniqueInputFieldNames { known_name_stack: Vec::new(), } } impl<'a, S> Visitor<'a, S> for UniqueInputFieldNames<'a> where S: ScalarValue, { fn enter_object_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedObject<'a, S>) { self.known_name_stack.push(HashMap::new()); } fn exit_object_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedObject<'a, S>) { self.known_name_stack.pop(); } fn enter_object_field( &mut self, ctx: &mut ValidatorContext<'a, S>, (field_name, _): (SpannedInput<'a, String>, SpannedInput>), ) { if let Some(ref mut known_names) = self.known_name_stack.last_mut() { match known_names.entry(field_name.item) { Entry::Occupied(e) => { ctx.report_error( &error_message(field_name.item), &[*e.get(), field_name.span.start], ); } Entry::Vacant(e) => { e.insert(field_name.span.start); } } } } } type SpannedInput<'a, T> = Spanning<&'a T, &'a Span>; type SpannedObject<'a, S> = SpannedInput<'a, Vec<(Spanning, Spanning>)>>; fn error_message(field_name: &str) -> String { format!("There can only be one input field named \"{field_name}\"") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn input_object_with_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: { f: true }) } "#, ); } #[test] fn same_input_object_within_two_args() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg1: { f: true }, arg2: { f: true }) } "#, ); } #[test] fn multiple_input_object_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: { f1: "value", f2: "value", f3: "value" }) } "#, ); } #[test] fn allows_for_nested_input_objects_with_similar_fields() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: { deep: { deep: { id: 1 } id: 1 } id: 1 }) } "#, ); } #[test] fn duplicate_input_object_fields() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: { f1: "value", f1: "value" }) } "#, &[RuleError::new( &error_message("f1"), &[ SourcePosition::new(38, 2, 25), SourcePosition::new(51, 2, 38), ], )], ); } #[test] fn many_duplicate_input_object_fields() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" { field(arg: { f1: "value", f1: "value", f1: "value" }) } "#, &[ RuleError::new( &error_message("f1"), &[ SourcePosition::new(38, 2, 25), SourcePosition::new(51, 2, 38), ], ), RuleError::new( &error_message("f1"), &[ SourcePosition::new(38, 2, 25), SourcePosition::new(64, 2, 51), ], ), ], ); } } juniper-0.16.2/src/validation/rules/unique_operation_names.rs000064400000000000000000000100151046102023000225550ustar 00000000000000use std::collections::hash_map::{Entry, HashMap}; use crate::{ ast::Operation, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct UniqueOperationNames<'a> { names: HashMap<&'a str, SourcePosition>, } pub fn factory<'a>() -> UniqueOperationNames<'a> { UniqueOperationNames { names: HashMap::new(), } } impl<'a, S> Visitor<'a, S> for UniqueOperationNames<'a> where S: ScalarValue, { fn enter_operation_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { if let Some(ref op_name) = op.item.name { match self.names.entry(op_name.item) { Entry::Occupied(e) => { ctx.report_error(&error_message(op_name.item), &[*e.get(), op.span.start]); } Entry::Vacant(e) => { e.insert(op.span.start); } } } } } fn error_message(op_name: &str) -> String { format!("There can only be one operation named {op_name}") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn no_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment fragA on Dog { name } "#, ); } #[test] fn one_anon_operation() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" { field } "#, ); } #[test] fn one_named_operation() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { field } "#, ); } #[test] fn multiple_operations() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { dog { name } } query Bar { dog { name } } "#, ); } #[test] fn multiple_operations_of_different_types() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { field } mutation Bar { field } "#, ); } #[test] fn fragment_and_operation_named_the_same() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { dog { ...Foo } } fragment Foo on Dog { name } "#, ); } #[test] fn multiple_operations_of_same_name() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { dog { name } } query Foo { human { name } } "#, &[RuleError::new( &error_message("Foo"), &[ SourcePosition::new(11, 1, 10), SourcePosition::new(96, 6, 10), ], )], ); } #[test] fn multiple_ops_of_same_name_of_different_types() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo { dog { name } } mutation Foo { testInput } "#, &[RuleError::new( &error_message("Foo"), &[ SourcePosition::new(11, 1, 10), SourcePosition::new(96, 6, 10), ], )], ); } } juniper-0.16.2/src/validation/rules/unique_variable_names.rs000064400000000000000000000062271046102023000223540ustar 00000000000000use std::collections::hash_map::{Entry, HashMap}; use crate::{ ast::{Operation, VariableDefinition}, parser::{SourcePosition, Spanning}, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct UniqueVariableNames<'a> { names: HashMap<&'a str, SourcePosition>, } pub fn factory<'a>() -> UniqueVariableNames<'a> { UniqueVariableNames { names: HashMap::new(), } } impl<'a, S> Visitor<'a, S> for UniqueVariableNames<'a> where S: ScalarValue, { fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { self.names = HashMap::new(); } fn enter_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, (var_name, _): &'a (Spanning<&'a str>, VariableDefinition), ) { match self.names.entry(var_name.item) { Entry::Occupied(e) => { ctx.report_error( &error_message(var_name.item), &[*e.get(), var_name.span.start], ); } Entry::Vacant(e) => { e.insert(var_name.span.start); } } } } fn error_message(var_name: &str) -> String { format!("There can only be one variable named {var_name}") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn unique_variable_names() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query A($x: Int, $y: String) { __typename } query B($x: String, $y: Int) { __typename } "#, ); } #[test] fn duplicate_variable_names() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query A($x: Int, $x: Int, $x: String) { __typename } query B($x: String, $x: Int) { __typename } query C($x: Int, $x: Int) { __typename } "#, &[ RuleError::new( &error_message("x"), &[ SourcePosition::new(19, 1, 18), SourcePosition::new(28, 1, 27), ], ), RuleError::new( &error_message("x"), &[ SourcePosition::new(19, 1, 18), SourcePosition::new(37, 1, 36), ], ), RuleError::new( &error_message("x"), &[ SourcePosition::new(82, 2, 18), SourcePosition::new(94, 2, 30), ], ), RuleError::new( &error_message("x"), &[ SourcePosition::new(136, 3, 18), SourcePosition::new(145, 3, 27), ], ), ], ); } } juniper-0.16.2/src/validation/rules/variables_are_input_types.rs000064400000000000000000000045201046102023000232520ustar 00000000000000use std::fmt; use crate::{ ast::VariableDefinition, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, }; pub struct UniqueVariableNames; pub fn factory() -> UniqueVariableNames { UniqueVariableNames } impl<'a, S> Visitor<'a, S> for UniqueVariableNames where S: ScalarValue, { fn enter_variable_definition( &mut self, ctx: &mut ValidatorContext<'a, S>, (var_name, var_def): &'a (Spanning<&'a str>, VariableDefinition), ) { if let Some(var_type) = ctx .schema .concrete_type_by_name(var_def.var_type.item.innermost_name()) { if !var_type.is_input() { ctx.report_error( &error_message(var_name.item, &var_def.var_type.item), &[var_def.var_type.span.start], ); } } } } fn error_message(var_name: impl fmt::Display, type_name: impl fmt::Display) -> String { format!("Variable \"{var_name}\" cannot be of non-input type \"{type_name}\"") } #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn input_types_are_valid() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { field(a: $a, b: $b, c: $c) } "#, ); } #[test] fn output_types_are_invalid() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { field(a: $a, b: $b, c: $c) } "#, &[ RuleError::new( &error_message("a", "Dog"), &[SourcePosition::new(25, 1, 24)], ), RuleError::new( &error_message("b", "[[CatOrDog!]]!"), &[SourcePosition::new(34, 1, 33)], ), RuleError::new( &error_message("c", "Pet"), &[SourcePosition::new(54, 1, 53)], ), ], ); } } juniper-0.16.2/src/validation/rules/variables_in_allowed_position.rs000064400000000000000000000365061046102023000241120ustar 00000000000000use std::{ borrow::Cow, collections::{HashMap, HashSet}, fmt, }; use crate::{ ast::{Document, Fragment, FragmentSpread, Operation, Type, VariableDefinition}, parser::Spanning, validation::{ValidatorContext, Visitor}, value::ScalarValue, Span, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Scope<'a> { Operation(Option<&'a str>), Fragment(&'a str), } pub fn factory<'a, S: fmt::Debug>() -> VariableInAllowedPosition<'a, S> { VariableInAllowedPosition { spreads: HashMap::new(), variable_usages: HashMap::new(), variable_defs: HashMap::new(), current_scope: None, } } pub struct VariableInAllowedPosition<'a, S: fmt::Debug + 'a> { spreads: HashMap, HashSet<&'a str>>, variable_usages: HashMap, Vec<(SpannedInput<'a, String>, Type<'a>)>>, #[allow(clippy::type_complexity)] variable_defs: HashMap, Vec<&'a (Spanning<&'a str>, VariableDefinition<'a, S>)>>, current_scope: Option>, } impl<'a, S: fmt::Debug> VariableInAllowedPosition<'a, S> { fn collect_incorrect_usages<'me>( &'me self, from: &Scope<'a>, var_defs: &[&'a (Spanning<&'a str>, VariableDefinition)], ctx: &mut ValidatorContext<'a, S>, visited: &mut HashSet>, ) { let mut to_visit = Vec::new(); if let Some(spreads) = self.collect_incorrect_usages_inner(from, var_defs, ctx, visited) { to_visit.push(spreads); } while let Some(spreads) = to_visit.pop() { for spread in spreads { if let Some(spreads) = self.collect_incorrect_usages_inner( &Scope::Fragment(spread), var_defs, ctx, visited, ) { to_visit.push(spreads); } } } } /// This function should be called only inside /// [`Self::collect_incorrect_usages()`], as it's a recursive function using /// heap instead of a stack. So, instead of the recursive call, we return a /// [`Vec`] that is visited inside [`Self::collect_incorrect_usages()`]. fn collect_incorrect_usages_inner<'me>( &'me self, from: &Scope<'a>, var_defs: &[&'a (Spanning<&'a str>, VariableDefinition)], ctx: &mut ValidatorContext<'a, S>, visited: &mut HashSet>, ) -> Option<&'me HashSet<&'a str>> { if visited.contains(from) { return None; } visited.insert(from.clone()); if let Some(usages) = self.variable_usages.get(from) { for (var_name, var_type) in usages { if let Some(&(var_def_name, var_def)) = var_defs.iter().find(|&&(n, _)| n.item == var_name.item) { let expected_type = match (&var_def.default_value, &var_def.var_type.item) { (&Some(_), Type::List(inner, expected_size)) => { Type::NonNullList(inner.clone(), *expected_size) } (&Some(_), Type::Named(inner)) => Type::NonNullNamed(Cow::Borrowed(inner)), (_, t) => t.clone(), }; if !ctx.schema.is_subtype(&expected_type, var_type) { ctx.report_error( &error_message(var_name.item, expected_type, var_type), &[var_def_name.span.start, var_name.span.start], ); } } } } self.spreads.get(from) } } impl<'a, S> Visitor<'a, S> for VariableInAllowedPosition<'a, S> where S: ScalarValue, { fn exit_document(&mut self, ctx: &mut ValidatorContext<'a, S>, _: &'a Document) { for (op_scope, var_defs) in &self.variable_defs { self.collect_incorrect_usages(op_scope, var_defs, ctx, &mut HashSet::new()); } } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) { self.current_scope = Some(Scope::Fragment(fragment.item.name.item)); } fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, op: &'a Spanning>, ) { self.current_scope = Some(Scope::Operation(op.item.name.as_ref().map(|s| s.item))); } fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) { if let Some(ref scope) = self.current_scope { self.spreads .entry(scope.clone()) .or_default() .insert(spread.item.name.item); } } fn enter_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, def: &'a (Spanning<&'a str>, VariableDefinition), ) { if let Some(ref scope) = self.current_scope { self.variable_defs .entry(scope.clone()) .or_default() .push(def); } } fn enter_variable_value( &mut self, ctx: &mut ValidatorContext<'a, S>, var_name: SpannedInput<'a, String>, ) { if let (Some(scope), Some(input_type)) = (&self.current_scope, ctx.current_input_type_literal()) { self.variable_usages .entry(scope.clone()) .or_default() .push((var_name, input_type.clone())); } } } fn error_message( var_name: impl fmt::Display, type_name: impl fmt::Display, expected_type_name: impl fmt::Display, ) -> String { format!( "Variable \"{var_name}\" of type \"{type_name}\" used in position expecting type \"{expected_type_name}\"", ) } type SpannedInput<'a, T> = Spanning<&'a T, &'a Span>; #[cfg(test)] mod tests { use super::{error_message, factory}; use crate::{ parser::SourcePosition, validation::{expect_fails_rule, expect_passes_rule, RuleError}, value::DefaultScalarValue, }; #[test] fn boolean_into_boolean() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($booleanArg: Boolean) { complicatedArgs { booleanArgField(booleanArg: $booleanArg) } } "#, ); } #[test] fn boolean_into_boolean_within_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $booleanArg) } query Query($booleanArg: Boolean) { complicatedArgs { ...booleanArgFrag } } "#, ); expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($booleanArg: Boolean) { complicatedArgs { ...booleanArgFrag } } fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $booleanArg) } "#, ); } #[test] fn non_null_boolean_into_boolean() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($nonNullBooleanArg: Boolean!) { complicatedArgs { booleanArgField(booleanArg: $nonNullBooleanArg) } } "#, ); } #[test] fn non_null_boolean_into_boolean_within_fragment() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" fragment booleanArgFrag on ComplicatedArgs { booleanArgField(booleanArg: $nonNullBooleanArg) } query Query($nonNullBooleanArg: Boolean!) { complicatedArgs { ...booleanArgFrag } } "#, ); } #[test] fn int_into_non_null_int_with_default() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($intArg: Int = 1) { complicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } } "#, ); } #[test] fn string_list_into_string_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringListVar: [String]) { complicatedArgs { stringListArgField(stringListArg: $stringListVar) } } "#, ); } #[test] fn non_null_string_list_into_string_list() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringListVar: [String!]) { complicatedArgs { stringListArgField(stringListArg: $stringListVar) } } "#, ); } #[test] fn string_into_string_list_in_item_position() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringVar: String) { complicatedArgs { stringListArgField(stringListArg: [$stringVar]) } } "#, ); } #[test] fn non_null_string_into_string_list_in_item_position() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringVar: String!) { complicatedArgs { stringListArgField(stringListArg: [$stringVar]) } } "#, ); } #[test] fn complex_input_into_complex_input() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($complexVar: ComplexInput) { complicatedArgs { complexArgField(complexArg: $complexVar) } } "#, ); } #[test] fn complex_input_into_complex_input_in_field_position() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($boolVar: Boolean = false) { complicatedArgs { complexArgField(complexArg: {requiredArg: $boolVar}) } } "#, ); } #[test] fn non_null_boolean_into_non_null_boolean_in_directive() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($boolVar: Boolean!) { dog @include(if: $boolVar) } "#, ); } #[test] fn boolean_in_non_null_in_directive_with_default() { expect_passes_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($boolVar: Boolean = false) { dog @include(if: $boolVar) } "#, ); } #[test] fn int_into_non_null_int() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($intArg: Int) { complicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } } "#, &[RuleError::new( &error_message("intArg", "Int", "Int!"), &[ SourcePosition::new(23, 1, 22), SourcePosition::new(117, 3, 48), ], )], ); } #[test] fn int_into_non_null_int_within_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment nonNullIntArgFieldFrag on ComplicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } query Query($intArg: Int) { complicatedArgs { ...nonNullIntArgFieldFrag } } "#, &[RuleError::new( &error_message("intArg", "Int", "Int!"), &[ SourcePosition::new(154, 5, 22), SourcePosition::new(110, 2, 46), ], )], ); } #[test] fn int_into_non_null_int_within_nested_fragment() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" fragment outerFrag on ComplicatedArgs { ...nonNullIntArgFieldFrag } fragment nonNullIntArgFieldFrag on ComplicatedArgs { nonNullIntArgField(nonNullIntArg: $intArg) } query Query($intArg: Int) { complicatedArgs { ...outerFrag } } "#, &[RuleError::new( &error_message("intArg", "Int", "Int!"), &[ SourcePosition::new(255, 9, 22), SourcePosition::new(211, 6, 46), ], )], ); } #[test] fn string_over_boolean() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringVar: String) { complicatedArgs { booleanArgField(booleanArg: $stringVar) } } "#, &[RuleError::new( &error_message("stringVar", "String", "Boolean"), &[ SourcePosition::new(23, 1, 22), SourcePosition::new(117, 3, 42), ], )], ); } #[test] fn string_into_string_list() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringVar: String) { complicatedArgs { stringListArgField(stringListArg: $stringVar) } } "#, &[RuleError::new( &error_message("stringVar", "String", "[String]"), &[ SourcePosition::new(23, 1, 22), SourcePosition::new(123, 3, 48), ], )], ); } #[test] fn boolean_into_non_null_boolean_in_directive() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($boolVar: Boolean) { dog @include(if: $boolVar) } "#, &[RuleError::new( &error_message("boolVar", "Boolean", "Boolean!"), &[ SourcePosition::new(23, 1, 22), SourcePosition::new(73, 2, 29), ], )], ); } #[test] fn string_into_non_null_boolean_in_directive() { expect_fails_rule::<_, _, DefaultScalarValue>( factory, r#" query Query($stringVar: String) { dog @include(if: $stringVar) } "#, &[RuleError::new( &error_message("stringVar", "String", "Boolean!"), &[ SourcePosition::new(23, 1, 22), SourcePosition::new(74, 2, 29), ], )], ); } } juniper-0.16.2/src/validation/test_harness.rs000064400000000000000000000662001046102023000173630ustar 00000000000000use std::mem; use crate::{ ast::{Document, FromInputValue, InputValue}, executor::Registry, parser::parse_document_source, schema::{ meta::{EnumValue, MetaType}, model::{DirectiveLocation, DirectiveType, RootNode}, }, types::{ base::{GraphQLType, GraphQLValue}, scalars::ID, }, validation::{visit, MultiVisitorNil, RuleError, ValidatorContext, Visitor}, value::ScalarValue, FieldError, GraphQLInputObject, IntoFieldError, }; struct Being; struct Pet; struct Canine; struct Unpopulated; struct Dog; struct Cat; struct Intelligent; struct Human; struct Alien; struct DogOrHuman; struct CatOrDog; struct HumanOrAlien; struct ComplicatedArgs; pub(crate) struct QueryRoot; #[derive(Debug, GraphQLInputObject)] struct TestInput { id: i32, name: String, } pub(crate) struct MutationRoot; pub(crate) struct SubscriptionRoot; #[derive(Debug)] enum DogCommand { Sit, Heel, Down, } #[derive(Debug)] enum FurColor { Brown, Black, Tan, Spotted, } #[allow(dead_code)] #[derive(Debug)] struct ComplexInput { required_field: bool, int_field: Option, string_field: Option, boolean_field: Option, string_list_field: Option>>, } impl GraphQLType for Being where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Being") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry .field::>("name", i) .argument(registry.arg::>("surname", i))]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for Being where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Pet where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Pet") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry .field::>("name", i) .argument(registry.arg::>("surname", i))]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for Pet where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Canine where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Canine") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry .field::>("name", i) .argument(registry.arg::>("surname", i))]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for Canine where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Unpopulated where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Unpopulated") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry .field::>("name", i) .argument(registry.arg::>("surname", i))]; registry .build_interface_type::(i, fields) .interfaces(&[registry.get_type::(i)]) .into_meta() } } impl GraphQLValue for Unpopulated where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for DogCommand where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("DogCommand") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry .build_enum_type::( i, &[ EnumValue::new("SIT"), EnumValue::new("HEEL"), EnumValue::new("DOWN"), ], ) .into_meta() } } impl GraphQLValue for DogCommand where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl FromInputValue for DogCommand where S: ScalarValue, { type Error = &'static str; fn from_input_value<'a>(v: &InputValue) -> Result { match v.as_enum_value() { Some("SIT") => Ok(DogCommand::Sit), Some("HEEL") => Ok(DogCommand::Heel), Some("DOWN") => Ok(DogCommand::Down), _ => Err("Unknown DogCommand"), } } } impl GraphQLType for Dog where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Dog") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("name", i) .argument(registry.arg::>("surname", i)), registry.field::>("nickname", i), registry.field::>("barkVolume", i), registry.field::>("barks", i), registry .field::>("doesKnowCommand", i) .argument(registry.arg::>("dogCommand", i)), registry .field::>("isHousetrained", i) .argument(registry.arg_with_default("atOtherHomes", &true, i)), registry .field::>("isAtLocation", i) .argument(registry.arg::>("x", i)) .argument(registry.arg::>("y", i)), ]; registry .build_object_type::(i, fields) .interfaces(&[ registry.get_type::(i), registry.get_type::(i), registry.get_type::(i), ]) .into_meta() } } impl GraphQLValue for Dog where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for FurColor where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("FurColor") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { registry .build_enum_type::( i, &[ EnumValue::new("BROWN"), EnumValue::new("BLACK"), EnumValue::new("TAN"), EnumValue::new("SPOTTED"), ], ) .into_meta() } } impl GraphQLValue for FurColor where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl FromInputValue for FurColor where S: ScalarValue, { type Error = &'static str; fn from_input_value<'a>(v: &InputValue) -> Result { match v.as_enum_value() { Some("BROWN") => Ok(FurColor::Brown), Some("BLACK") => Ok(FurColor::Black), Some("TAN") => Ok(FurColor::Tan), Some("SPOTTED") => Ok(FurColor::Spotted), _ => Err("Unknown FurColor"), } } } impl GraphQLType for Cat where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Cat") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("name", i) .argument(registry.arg::>("surname", i)), registry.field::>("nickname", i), registry.field::>("meows", i), registry.field::>("meowVolume", i), registry.field::>("furColor", i), ]; registry .build_object_type::(i, fields) .interfaces(&[registry.get_type::(i), registry.get_type::(i)]) .into_meta() } } impl GraphQLValue for Cat where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for CatOrDog where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("CatOrDog") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let types = &[registry.get_type::(i), registry.get_type::(i)]; registry.build_union_type::(i, types).into_meta() } } impl GraphQLValue for CatOrDog where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Intelligent where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Intelligent") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[registry.field::>("iq", i)]; registry.build_interface_type::(i, fields).into_meta() } } impl GraphQLValue for Intelligent where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Human where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Human") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("name", i) .argument(registry.arg::>("surname", i)), registry.field::>>>("pets", i), registry.field::>>("relatives", i), registry.field::>("iq", i), ]; registry .build_object_type::(i, fields) .interfaces(&[ registry.get_type::(i), registry.get_type::(i), ]) .into_meta() } } impl GraphQLValue for Human where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for Alien where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("Alien") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("name", i) .argument(registry.arg::>("surname", i)), registry.field::>("iq", i), registry.field::>("numEyes", i), ]; registry .build_object_type::(i, fields) .interfaces(&[ registry.get_type::(i), registry.get_type::(i), ]) .into_meta() } } impl GraphQLValue for Alien where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for DogOrHuman where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("DogOrHuman") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let types = &[registry.get_type::(i), registry.get_type::(i)]; registry.build_union_type::(i, types).into_meta() } } impl GraphQLValue for DogOrHuman where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for HumanOrAlien where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("HumanOrAlien") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let types = &[registry.get_type::(i), registry.get_type::(i)]; registry.build_union_type::(i, types).into_meta() } } impl GraphQLValue for HumanOrAlien where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for ComplexInput where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("ComplexInput") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry.arg::("requiredField", i), registry.arg::>("intField", i), registry.arg::>("stringField", i), registry.arg::>("booleanField", i), registry.arg::>>>("stringListField", i), ]; registry .build_input_object_type::(i, fields) .into_meta() } } impl GraphQLValue for ComplexInput where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl FromInputValue for ComplexInput where S: ScalarValue, { type Error = FieldError; fn from_input_value<'a>(v: &InputValue) -> Result { let obj = v.to_object_value().ok_or("Expected object")?; Ok(ComplexInput { required_field: obj .get("requiredField") .map(|v| v.convert()) .transpose()? .ok_or("Expected requiredField")?, int_field: obj .get("intField") .map(|v| v.convert()) .transpose()? .ok_or("Expected intField")?, string_field: obj .get("stringField") .map(|v| v.convert()) .transpose()? .ok_or("Expected stringField")?, boolean_field: obj .get("booleanField") .map(|v| v.convert()) .transpose()? .ok_or("Expected booleanField")?, string_list_field: obj .get("stringListField") .map(|v| v.convert().map_err(IntoFieldError::into_field_error)) .transpose()? .ok_or("Expected stringListField")?, }) } } impl GraphQLType for ComplicatedArgs where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("ComplicatedArgs") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("intArgField", i) .argument(registry.arg::>("intArg", i)), registry .field::>("nonNullIntArgField", i) .argument(registry.arg::("nonNullIntArg", i)), registry .field::>("stringArgField", i) .argument(registry.arg::>("stringArg", i)), registry .field::>("booleanArgField", i) .argument(registry.arg::>("booleanArg", i)), registry .field::>("enumArgField", i) .argument(registry.arg::>("enumArg", i)), registry .field::>("floatArgField", i) .argument(registry.arg::>("floatArg", i)), registry .field::>("idArgField", i) .argument(registry.arg::>("idArg", i)), registry .field::>("stringListArgField", i) .argument(registry.arg::>>>("stringListArg", i)), registry .field::>("nonNullStringListArgField", i) .argument(registry.arg::>("nonNullStringListArg", i)), registry .field::>("complexArgField", i) .argument(registry.arg::>("complexArg", i)), registry .field::>("multipleReqs", i) .argument(registry.arg::("req1", i)) .argument(registry.arg::("req2", i)), registry .field::>("multipleOpts", i) .argument(registry.arg_with_default("opt1", &0i32, i)) .argument(registry.arg_with_default("opt2", &0i32, i)), registry .field::>("multipleOptAndReq", i) .argument(registry.arg::("req1", i)) .argument(registry.arg::("req2", i)) .argument(registry.arg_with_default("opt1", &0i32, i)) .argument(registry.arg_with_default("opt2", &0i32, i)), ]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for ComplicatedArgs where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for QueryRoot where S: ScalarValue, { fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = &[ registry .field::>("human", i) .argument(registry.arg::>("id", i)), registry.field::>("alien", i), registry.field::>("dog", i), registry.field::>("cat", i), registry.field::>("pet", i), registry.field::>("catOrDog", i), registry.field::>("dorOrHuman", i), registry.field::>("humanOrAlien", i), registry.field::>("complicatedArgs", i), ]; registry.build_object_type::(i, fields).into_meta() } } impl GraphQLValue for QueryRoot where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for MutationRoot where S: ScalarValue, { fn name(_: &()) -> Option<&str> { Some("MutationRoot") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let _ = registry.get_type::(i); let fields = [registry.field::("testInput", i).argument( registry.arg_with_default::( "input", &TestInput { id: 423, name: String::from("foo"), }, i, ), )]; registry.build_object_type::(i, &fields).into_meta() } } impl GraphQLValue for MutationRoot where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } impl GraphQLType for SubscriptionRoot where S: ScalarValue, { fn name(_: &()) -> Option<&str> { Some("SubscriptionRoot") } fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> where S: 'r, { let fields = []; registry.build_object_type::(i, &fields).into_meta() } } impl GraphQLValue for SubscriptionRoot where S: ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { ::name(info) } } pub fn validate<'a, Q, M, Sub, F, S>(r: Q, m: M, s: Sub, q: &'a str, visit_fn: F) -> Vec where S: ScalarValue + 'a, Q: GraphQLType, M: GraphQLType, Sub: GraphQLType, F: FnOnce(&mut ValidatorContext<'a, S>, &'a Document), { let mut root = RootNode::new_with_scalar_value(r, m, s); root.schema.add_directive(DirectiveType::new( "onQuery", &[DirectiveLocation::Query], &[], false, )); root.schema.add_directive(DirectiveType::new( "onMutation", &[DirectiveLocation::Mutation], &[], false, )); root.schema.add_directive(DirectiveType::new( "onField", &[DirectiveLocation::Field], &[], false, )); root.schema.add_directive(DirectiveType::new( "onFragmentDefinition", &[DirectiveLocation::FragmentDefinition], &[], false, )); root.schema.add_directive(DirectiveType::new( "onFragmentSpread", &[DirectiveLocation::FragmentSpread], &[], false, )); root.schema.add_directive(DirectiveType::new( "onInlineFragment", &[DirectiveLocation::InlineFragment], &[], false, )); let doc = parse_document_source(q, &root.schema) .unwrap_or_else(|_| panic!("Parse error on input {q:#?}")); let mut ctx = ValidatorContext::new(unsafe { mem::transmute(&root.schema) }, &doc); visit_fn(&mut ctx, unsafe { mem::transmute(doc.as_slice()) }); ctx.into_errors() } pub fn expect_passes_rule<'a, V, F, S>(factory: F, q: &'a str) where S: ScalarValue + 'a, V: Visitor<'a, S> + 'a, F: Fn() -> V, { expect_passes_rule_with_schema(QueryRoot, MutationRoot, SubscriptionRoot, factory, q); } pub fn expect_passes_fn<'a, F, S>(visit_fn: F, q: &'a str) where S: ScalarValue + 'a, F: FnOnce(&mut ValidatorContext<'a, S>, &'a Document), { expect_passes_fn_with_schema(QueryRoot, MutationRoot, SubscriptionRoot, visit_fn, q); } pub fn expect_passes_rule_with_schema<'a, Q, M, Sub, V, F, S>( r: Q, m: M, s: Sub, factory: F, q: &'a str, ) where S: ScalarValue + 'a, Q: GraphQLType, M: GraphQLType, Sub: GraphQLType, V: Visitor<'a, S> + 'a, F: FnOnce() -> V, { let errs = validate(r, m, s, q, move |ctx, doc| { let mut mv = MultiVisitorNil.with(factory()); visit(&mut mv, ctx, unsafe { mem::transmute(doc) }); }); if !errs.is_empty() { print_errors(&errs); panic!("Expected rule to pass, but errors found"); } } pub fn expect_passes_fn_with_schema<'a, Q, M, Sub, F, S>( r: Q, m: M, s: Sub, visit_fn: F, q: &'a str, ) where S: ScalarValue + 'a, Q: GraphQLType, M: GraphQLType, Sub: GraphQLType, F: FnOnce(&mut ValidatorContext<'a, S>, &'a Document), { let errs = validate(r, m, s, q, visit_fn); if !errs.is_empty() { print_errors(&errs); panic!("Expected `visit_fn` to pass, but errors found"); } } pub fn expect_fails_rule<'a, V, F, S>(factory: F, q: &'a str, expected_errors: &[RuleError]) where S: ScalarValue + 'a, V: Visitor<'a, S> + 'a, F: Fn() -> V, { expect_fails_rule_with_schema(QueryRoot, MutationRoot, factory, q, expected_errors); } pub fn expect_fails_fn<'a, F, S>(visit_fn: F, q: &'a str, expected_errors: &[RuleError]) where S: ScalarValue + 'a, F: FnOnce(&mut ValidatorContext<'a, S>, &'a Document), { expect_fails_fn_with_schema(QueryRoot, MutationRoot, visit_fn, q, expected_errors); } pub fn expect_fails_rule_with_schema<'a, Q, M, V, F, S>( r: Q, m: M, factory: F, q: &'a str, expected_errors: &[RuleError], ) where S: ScalarValue + 'a, Q: GraphQLType, M: GraphQLType, V: Visitor<'a, S> + 'a, F: FnOnce() -> V, { let errs = validate( r, m, crate::EmptySubscription::::new(), q, move |ctx, doc| { let mut mv = MultiVisitorNil.with(factory()); visit(&mut mv, ctx, unsafe { mem::transmute(doc) }); }, ); if errs.is_empty() { panic!("Expected rule to fail, but no errors were found"); } else if errs != expected_errors { println!("==> Expected errors:"); print_errors(expected_errors); println!("\n==> Actual errors:"); print_errors(&errs); panic!("Unexpected set of errors found"); } } pub fn expect_fails_fn_with_schema<'a, Q, M, F, S>( r: Q, m: M, visit_fn: F, q: &'a str, expected_errors: &[RuleError], ) where S: ScalarValue + 'a, Q: GraphQLType, M: GraphQLType, F: FnOnce(&mut ValidatorContext<'a, S>, &'a Document), { let errs = validate(r, m, crate::EmptySubscription::::new(), q, visit_fn); if errs.is_empty() { panic!("Expected `visit_fn` to fail, but no errors were found"); } else if errs != expected_errors { println!("==> Expected errors:"); print_errors(expected_errors); println!("\n==> Actual errors:"); print_errors(&errs); panic!("Unexpected set of errors found"); } } fn print_errors(errs: &[RuleError]) { for err in errs { for p in err.locations() { print!("[{:>3},{:>3},{:>3}] ", p.index(), p.line(), p.column()); } println!("{}", err.message()); } } juniper-0.16.2/src/validation/traits.rs000064400000000000000000000112771046102023000161730ustar 00000000000000use crate::{ ast::{ Directive, Document, Field, Fragment, FragmentSpread, InlineFragment, InputValue, Operation, Selection, VariableDefinition, }, parser::Spanning, validation::ValidatorContext, value::ScalarValue, Span, }; #[doc(hidden)] pub trait Visitor<'a, S> where S: ScalarValue, { fn enter_document(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Document) {} fn exit_document(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Document) {} fn enter_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn exit_operation_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn enter_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn exit_fragment_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn enter_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, VariableDefinition), ) { } fn exit_variable_definition( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, VariableDefinition), ) { } fn enter_directive(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) {} fn exit_directive(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) {} fn enter_argument( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, Spanning>), ) { } fn exit_argument( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a (Spanning<&'a str>, Spanning>), ) { } fn enter_selection_set(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a [Selection]) {} fn exit_selection_set(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a [Selection]) {} fn enter_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) {} fn exit_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) {} fn enter_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn exit_fragment_spread( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn enter_inline_fragment( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn exit_inline_fragment( &mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>, ) { } fn enter_null_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, ()>) {} fn exit_null_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, ()>) {} fn enter_scalar_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, S>) {} fn exit_scalar_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, S>) {} fn enter_enum_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, String>) {} fn exit_enum_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, String>) {} fn enter_variable_value( &mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, String>, ) { } fn exit_variable_value( &mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, String>, ) { } fn enter_list_value( &mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, Vec>>>, ) { } fn exit_list_value( &mut self, _: &mut ValidatorContext<'a, S>, _: SpannedInput<'a, Vec>>>, ) { } fn enter_object_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedObject<'a, S>) {} fn exit_object_value(&mut self, _: &mut ValidatorContext<'a, S>, _: SpannedObject<'a, S>) {} fn enter_object_field( &mut self, _: &mut ValidatorContext<'a, S>, _: (SpannedInput<'a, String>, SpannedInput<'a, InputValue>), ) { } fn exit_object_field( &mut self, _: &mut ValidatorContext<'a, S>, _: (SpannedInput<'a, String>, SpannedInput<'a, InputValue>), ) { } } type SpannedInput<'a, T> = Spanning<&'a T, &'a Span>; type SpannedObject<'a, S> = SpannedInput<'a, Vec<(Spanning, Spanning>)>>; juniper-0.16.2/src/validation/visitor.rs000064400000000000000000000303071046102023000163570ustar 00000000000000use std::borrow::Cow; use crate::{ ast::{ Arguments, Definition, Directive, Document, Field, Fragment, FragmentSpread, InlineFragment, InputValue, Operation, OperationType, Selection, Type, VariableDefinitions, }, parser::Spanning, schema::meta::Argument, validation::{multi_visitor::MultiVisitorCons, ValidatorContext, Visitor}, value::ScalarValue, }; #[doc(hidden)] pub fn visit<'a, A, B, S>( v: &mut MultiVisitorCons, ctx: &mut ValidatorContext<'a, S>, d: &'a Document, ) where S: ScalarValue, MultiVisitorCons: Visitor<'a, S>, { v.enter_document(ctx, d); visit_definitions(v, ctx, d); v.exit_document(ctx, d); } fn visit_definitions<'a, S, V>(v: &mut V, ctx: &mut ValidatorContext<'a, S>, d: &'a [Definition]) where S: ScalarValue, V: Visitor<'a, S>, { for def in d { let def_type = match *def { Definition::Fragment(Spanning { item: Fragment { type_condition: Spanning { item: name, .. }, .. }, .. }) => Some(Type::NonNullNamed(Cow::Borrowed(name))), Definition::Operation(Spanning { item: Operation { operation_type: OperationType::Query, .. }, .. }) => Some(Type::NonNullNamed(Cow::Borrowed( ctx.schema.concrete_query_type().name().unwrap(), ))), Definition::Operation(Spanning { item: Operation { operation_type: OperationType::Mutation, .. }, .. }) => ctx .schema .concrete_mutation_type() .map(|t| Type::NonNullNamed(Cow::Borrowed(t.name().unwrap()))), Definition::Operation(Spanning { item: Operation { operation_type: OperationType::Subscription, .. }, .. }) => ctx .schema .concrete_subscription_type() .map(|t| Type::NonNullNamed(Cow::Borrowed(t.name().unwrap()))), }; ctx.with_pushed_type(def_type.as_ref(), |ctx| { enter_definition(v, ctx, def); visit_definition(v, ctx, def); exit_definition(v, ctx, def); }); } } fn enter_definition<'a, S, V>(v: &mut V, ctx: &mut ValidatorContext<'a, S>, def: &'a Definition) where S: ScalarValue, V: Visitor<'a, S>, { match *def { Definition::Operation(ref op) => v.enter_operation_definition(ctx, op), Definition::Fragment(ref f) => v.enter_fragment_definition(ctx, f), } } fn exit_definition<'a, S, V>(v: &mut V, ctx: &mut ValidatorContext<'a, S>, def: &'a Definition) where S: ScalarValue, V: Visitor<'a, S>, { match *def { Definition::Operation(ref op) => v.exit_operation_definition(ctx, op), Definition::Fragment(ref f) => v.exit_fragment_definition(ctx, f), } } fn visit_definition<'a, S, V>(v: &mut V, ctx: &mut ValidatorContext<'a, S>, def: &'a Definition) where S: ScalarValue, V: Visitor<'a, S>, { match *def { Definition::Operation(ref op) => { visit_variable_definitions(v, ctx, &op.item.variable_definitions); visit_directives(v, ctx, &op.item.directives); visit_selection_set(v, ctx, &op.item.selection_set); } Definition::Fragment(ref f) => { visit_directives(v, ctx, &f.item.directives); visit_selection_set(v, ctx, &f.item.selection_set); } } } fn visit_variable_definitions<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, defs: &'a Option>>, ) where S: ScalarValue, V: Visitor<'a, S>, { if let Some(ref defs) = *defs { for def in defs.item.iter() { let var_type = def.1.var_type.item.clone(); ctx.with_pushed_input_type(Some(&var_type), |ctx| { v.enter_variable_definition(ctx, def); if let Some(ref default_value) = def.1.default_value { visit_input_value(v, ctx, default_value); } if let Some(dirs) = &def.1.directives { for directive in dirs { let directive_arguments = ctx .schema .directive_by_name(directive.item.name.item) .map(|d| &d.arguments); v.enter_directive(ctx, directive); visit_arguments(v, ctx, directive_arguments, &directive.item.arguments); v.exit_directive(ctx, directive); } } v.exit_variable_definition(ctx, def); }) } } } fn visit_directives<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, directives: &'a Option>>>, ) where S: ScalarValue, V: Visitor<'a, S>, { if let Some(ref directives) = *directives { for directive in directives { let directive_arguments = ctx .schema .directive_by_name(directive.item.name.item) .map(|d| &d.arguments); v.enter_directive(ctx, directive); visit_arguments(v, ctx, directive_arguments, &directive.item.arguments); v.exit_directive(ctx, directive); } } } fn visit_arguments<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, meta_args: Option<&Vec>>, arguments: &'a Option>>, ) where S: ScalarValue, V: Visitor<'a, S>, { if let Some(ref arguments) = *arguments { for argument in arguments.item.iter() { let arg_type = meta_args .and_then(|args| args.iter().find(|a| a.name == argument.0.item)) .map(|a| &a.arg_type); ctx.with_pushed_input_type(arg_type, |ctx| { v.enter_argument(ctx, argument); visit_input_value(v, ctx, &argument.1); v.exit_argument(ctx, argument); }) } } } fn visit_selection_set<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, selection_set: &'a [Selection], ) where S: ScalarValue, V: Visitor<'a, S>, { ctx.with_pushed_parent_type(|ctx| { v.enter_selection_set(ctx, selection_set); for selection in selection_set.iter() { visit_selection(v, ctx, selection); } v.exit_selection_set(ctx, selection_set); }); } fn visit_selection<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, selection: &'a Selection, ) where S: ScalarValue, V: Visitor<'a, S>, { match *selection { Selection::Field(ref field) => visit_field(v, ctx, field), Selection::FragmentSpread(ref spread) => visit_fragment_spread(v, ctx, spread), Selection::InlineFragment(ref fragment) => visit_inline_fragment(v, ctx, fragment), } } fn visit_field<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { let meta_field = ctx .parent_type() .and_then(|t| t.field_by_name(field.item.name.item)); let field_type = meta_field.map(|f| &f.field_type); let field_args = meta_field.and_then(|f| f.arguments.as_ref()); ctx.with_pushed_type(field_type, |ctx| { v.enter_field(ctx, field); visit_arguments(v, ctx, field_args, &field.item.arguments); visit_directives(v, ctx, &field.item.directives); if let Some(ref selection_set) = field.item.selection_set { visit_selection_set(v, ctx, selection_set); } v.exit_field(ctx, field); }); } fn visit_fragment_spread<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, spread: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { v.enter_fragment_spread(ctx, spread); visit_directives(v, ctx, &spread.item.directives); v.exit_fragment_spread(ctx, spread); } fn visit_inline_fragment<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, fragment: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { let mut visit_fn = move |ctx: &mut ValidatorContext<'a, S>| { v.enter_inline_fragment(ctx, fragment); visit_directives(v, ctx, &fragment.item.directives); visit_selection_set(v, ctx, &fragment.item.selection_set); v.exit_inline_fragment(ctx, fragment); }; if let Some(Spanning { item: type_name, .. }) = fragment.item.type_condition { ctx.with_pushed_type( Some(&Type::NonNullNamed(Cow::Borrowed(type_name))), visit_fn, ); } else { visit_fn(ctx); } } fn visit_input_value<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, input_value: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { enter_input_value(v, ctx, input_value); match input_value.item { InputValue::Object(ref fields) => { for (key, value) in fields { let inner_type = ctx .current_input_type_literal() .and_then(|t| match *t { Type::NonNullNamed(ref name) | Type::Named(ref name) => { ctx.schema.concrete_type_by_name(name) } _ => None, }) .and_then(|ct| ct.input_field_by_name(&key.item)) .map(|f| &f.arg_type); ctx.with_pushed_input_type(inner_type, |ctx| { v.enter_object_field(ctx, (key.as_ref(), value.as_ref())); visit_input_value(v, ctx, value); v.exit_object_field(ctx, (key.as_ref(), value.as_ref())); }) } } InputValue::List(ref ls) => { let inner_type = ctx.current_input_type_literal().and_then(|t| match *t { Type::List(ref inner, _) | Type::NonNullList(ref inner, _) => { Some(inner.as_ref().clone()) } _ => None, }); ctx.with_pushed_input_type(inner_type.as_ref(), |ctx| { for value in ls { visit_input_value(v, ctx, value); } }) } _ => (), } exit_input_value(v, ctx, input_value); } fn enter_input_value<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, input_value: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { use crate::InputValue::*; let span = &input_value.span; match &input_value.item { Null => v.enter_null_value(ctx, Spanning { span, item: &() }), Scalar(item) => v.enter_scalar_value(ctx, Spanning { span, item }), Enum(item) => v.enter_enum_value(ctx, Spanning { span, item }), Variable(item) => v.enter_variable_value(ctx, Spanning { span, item }), List(item) => v.enter_list_value(ctx, Spanning { span, item }), Object(item) => v.enter_object_value(ctx, Spanning { span, item }), } } fn exit_input_value<'a, S, V>( v: &mut V, ctx: &mut ValidatorContext<'a, S>, input_value: &'a Spanning>, ) where S: ScalarValue, V: Visitor<'a, S>, { use crate::InputValue::*; let span = &input_value.span; match &input_value.item { Null => v.exit_null_value(ctx, Spanning { span, item: &() }), Scalar(item) => v.exit_scalar_value(ctx, Spanning { span, item }), Enum(item) => v.exit_enum_value(ctx, Spanning { span, item }), Variable(item) => v.exit_variable_value(ctx, Spanning { span, item }), List(item) => v.exit_list_value(ctx, Spanning { span, item }), Object(item) => v.exit_object_value(ctx, Spanning { span, item }), } } juniper-0.16.2/src/value/mod.rs000064400000000000000000000223301046102023000144160ustar 00000000000000mod object; mod scalar; use std::{any::TypeId, borrow::Cow, fmt, mem}; use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, }; pub use self::{ object::Object, scalar::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue}, }; /// Serializable value returned from query and field execution. /// /// Used by the execution engine and resolvers to build up the response /// structure. Similar to the `Json` type found in the serialize crate. /// /// It is also similar to the `InputValue` type, but can not contain enum /// values or variables. Also, lists and objects do not contain any location /// information since they are generated by resolving fields and values rather /// than parsing a source query. #[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] pub enum Value { Null, Scalar(S), List(Vec>), Object(Object), } impl Value { // CONSTRUCTORS /// Construct a null value. pub fn null() -> Self { Self::Null } /// Construct a list value. pub fn list(l: Vec) -> Self { Self::List(l) } /// Construct an object value. pub fn object(o: Object) -> Self { Self::Object(o) } /// Construct a scalar value pub fn scalar(s: T) -> Self where S: From, { Self::Scalar(s.into()) } // DISCRIMINATORS /// Does this value represent null? pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } /// View the underlying scalar value if present pub fn as_scalar_value<'a, T>(&'a self) -> Option<&'a T> where Option<&'a T>: From<&'a S>, { match self { Self::Scalar(s) => s.into(), _ => None, } } /// View the underlying float value, if present. pub fn as_float_value(&self) -> Option where S: ScalarValue, { match self { Self::Scalar(s) => s.as_float(), _ => None, } } /// View the underlying object value, if present. pub fn as_object_value(&self) -> Option<&Object> { match self { Self::Object(o) => Some(o), _ => None, } } /// Convert this value into an Object. /// /// Returns None if value is not an Object. pub fn into_object(self) -> Option> { match self { Self::Object(o) => Some(o), _ => None, } } /// Mutable view into the underlying object value, if present. pub fn as_mut_object_value(&mut self) -> Option<&mut Object> { match self { Self::Object(o) => Some(o), _ => None, } } /// View the underlying list value, if present. pub fn as_list_value(&self) -> Option<&Vec> { match self { Self::List(l) => Some(l), _ => None, } } /// View the underlying scalar value, if present pub fn as_scalar(&self) -> Option<&S> { match self { Self::Scalar(s) => Some(s), _ => None, } } /// View the underlying string value, if present. pub fn as_string_value<'a>(&'a self) -> Option<&'a str> where Option<&'a String>: From<&'a S>, { self.as_scalar_value::().map(String::as_str) } /// Maps the [`ScalarValue`] type of this [`Value`] into the specified one. pub fn map_scalar_value(self) -> Value where S: ScalarValue, Into: ScalarValue, { if TypeId::of::() == TypeId::of::() { // SAFETY: This is safe, because we're transmuting the value into // itself, so no invariants may change and we're just // satisfying the type checker. // As `mem::transmute_copy` creates a copy of data, we need // `mem::ManuallyDrop` here to omit double-free when // `S: Drop`. let val = mem::ManuallyDrop::new(self); unsafe { mem::transmute_copy(&*val) } } else { match self { Self::Null => Value::Null, Self::Scalar(s) => Value::Scalar(s.into_another()), Self::List(l) => Value::List(l.into_iter().map(Value::map_scalar_value).collect()), Self::Object(o) => Value::Object( o.into_iter() .map(|(k, v)| (k, v.map_scalar_value())) .collect(), ), } } } } impl ToInputValue for Value { fn to_input_value(&self) -> InputValue { match self { Self::Null => InputValue::Null, Self::Scalar(s) => InputValue::Scalar(s.clone()), Self::List(l) => InputValue::List( l.iter() .map(|x| Spanning::unlocated(x.to_input_value())) .collect(), ), Self::Object(o) => InputValue::Object( o.iter() .map(|(k, v)| { ( Spanning::unlocated(k.clone()), Spanning::unlocated(v.to_input_value()), ) }) .collect(), ), } } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Null => write!(f, "null"), Self::Scalar(s) => { if let Some(string) = s.as_string() { write!(f, "\"{string}\"") } else { write!(f, "{s}") } } Self::List(list) => { write!(f, "[")?; for (idx, item) in list.iter().enumerate() { write!(f, "{item}")?; if idx < list.len() - 1 { write!(f, ", ")?; } } write!(f, "]")?; Ok(()) } Self::Object(obj) => { write!(f, "{{")?; for (idx, (key, value)) in obj.iter().enumerate() { write!(f, "\"{key}\": {value}")?; if idx < obj.field_count() - 1 { write!(f, ", ")?; } } write!(f, "}}")?; Ok(()) } } } } impl From> for Value where Self: From, { fn from(v: Option) -> Self { match v { Some(v) => v.into(), None => Self::Null, } } } impl<'a, S: From> From<&'a str> for Value { fn from(s: &'a str) -> Self { Self::scalar(s.to_owned()) } } impl<'a, S: From> From> for Value { fn from(s: Cow<'a, str>) -> Self { Self::scalar(s.into_owned()) } } impl> From for Value { fn from(s: String) -> Self { Self::scalar(s) } } impl> From for Value { fn from(i: i32) -> Self { Self::scalar(i) } } impl> From for Value { fn from(f: f64) -> Self { Self::scalar(f) } } impl> From for Value { fn from(b: bool) -> Self { Self::scalar(b) } } #[cfg(test)] mod tests { use crate::graphql_value; use super::Value; #[test] fn display_null() { let s: Value = graphql_value!(null); assert_eq!(s.to_string(), "null"); } #[test] fn display_int() { let s: Value = graphql_value!(123); assert_eq!(s.to_string(), "123"); } #[test] fn display_float() { let s: Value = graphql_value!(123.456); assert_eq!(s.to_string(), "123.456"); } #[test] fn display_string() { let s: Value = graphql_value!("foo"); assert_eq!(s.to_string(), "\"foo\""); } #[test] fn display_bool() { let s: Value = graphql_value!(false); assert_eq!(s.to_string(), "false"); let s: Value = graphql_value!(true); assert_eq!(s.to_string(), "true"); } #[test] fn display_list() { let s: Value = graphql_value!([1, null, "foo"]); assert_eq!(s.to_string(), "[1, null, \"foo\"]"); } #[test] fn display_list_one_element() { let s: Value = graphql_value!([1]); assert_eq!(s.to_string(), "[1]"); } #[test] fn display_list_empty() { let s: Value = graphql_value!([]); assert_eq!(s.to_string(), "[]"); } #[test] fn display_object() { let s: Value = graphql_value!({ "int": 1, "null": null, "string": "foo", }); assert_eq!( s.to_string(), r#"{"int": 1, "null": null, "string": "foo"}"#, ); } #[test] fn display_object_one_field() { let s: Value = graphql_value!({ "int": 1, }); assert_eq!(s.to_string(), r#"{"int": 1}"#); } #[test] fn display_object_empty() { let s: Value = graphql_value!({}); assert_eq!(s.to_string(), r#"{}"#); } } juniper-0.16.2/src/value/object.rs000064400000000000000000000054221046102023000151100ustar 00000000000000use std::mem; use super::Value; use indexmap::map::{IndexMap, IntoIter}; /// An Object value #[derive(Debug, Clone, PartialEq)] pub struct Object { key_value_list: IndexMap>, } impl Object { /// Create a new Object value with a fixed number of /// preallocated slots for field-value pairs pub fn with_capacity(size: usize) -> Self { Object { key_value_list: IndexMap::with_capacity(size), } } /// Add a new field with a value /// /// If there is already a field for the given key /// any both values are objects, they are merged. /// /// Otherwise the existing value is replaced and /// returned. pub fn add_field(&mut self, k: K, value: Value) -> Option> where K: AsRef + Into, { if let Some(v) = self.key_value_list.get_mut(k.as_ref()) { Some(mem::replace(v, value)) } else { self.key_value_list.insert(k.into(), value) } } /// Check if the object already contains a field with the given name pub fn contains_field>(&self, k: K) -> bool { self.key_value_list.contains_key(k.as_ref()) } /// Get a iterator over all field value pairs pub fn iter(&self) -> impl Iterator)> { self.key_value_list.iter() } /// Get a iterator over all mutable field value pairs pub fn iter_mut(&mut self) -> impl Iterator)> { self.key_value_list.iter_mut() } /// Get the current number of fields pub fn field_count(&self) -> usize { self.key_value_list.len() } /// Get the value for a given field pub fn get_field_value>(&self, key: K) -> Option<&Value> { self.key_value_list.get(key.as_ref()) } /// Get the mutable value for a given field pub fn get_mut_field_value>(&mut self, key: K) -> Option<&mut Value> { self.key_value_list.get_mut(key.as_ref()) } } impl IntoIterator for Object { type Item = (String, Value); type IntoIter = IntoIter>; fn into_iter(self) -> Self::IntoIter { self.key_value_list.into_iter() } } impl From> for Value { fn from(o: Object) -> Self { Self::Object(o) } } impl FromIterator<(K, Value)> for Object where K: AsRef + Into, { fn from_iter(iter: I) -> Self where I: IntoIterator)>, { let iter = iter.into_iter(); let mut ret = Self { key_value_list: IndexMap::with_capacity(iter.size_hint().0), }; for (k, v) in iter { ret.add_field(k, v); } ret } } juniper-0.16.2/src/value/scalar.rs000064400000000000000000000221311046102023000151030ustar 00000000000000use std::{borrow::Cow, fmt}; use serde::{de::DeserializeOwned, Serialize}; use crate::parser::{ParseError, ScalarToken}; pub use juniper_codegen::ScalarValue; /// The result of converting a string into a scalar value pub type ParseScalarResult = Result; /// A trait used to convert a `ScalarToken` into a certain scalar value type pub trait ParseScalarValue { /// See the trait documentation fn from_str(value: ScalarToken<'_>) -> ParseScalarResult; } /// A trait marking a type that could be used as internal representation of /// scalar values in juniper /// /// The main objective of this abstraction is to allow other libraries to /// replace the default representation with something that better fits their /// needs. /// There is a custom derive (`#[derive(`[`ScalarValue`]`)]`) available that /// implements most of the required traits automatically for a enum representing /// a scalar value. However, [`Serialize`] and [`Deserialize`] implementations /// are expected to be provided. /// /// # Implementing a new scalar value representation /// The preferred way to define a new scalar value representation is /// defining a enum containing a variant for each type that needs to be /// represented at the lowest level. /// The following example introduces an new variant that is able to store 64 bit /// integers. /// /// ```rust /// # use std::fmt; /// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; /// # use juniper::ScalarValue; /// # /// #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] /// #[serde(untagged)] /// enum MyScalarValue { /// #[value(as_float, as_int)] /// Int(i32), /// Long(i64), /// #[value(as_float)] /// Float(f64), /// #[value(as_str, as_string, into_string)] /// String(String), /// #[value(as_bool)] /// Boolean(bool), /// } /// /// impl<'de> Deserialize<'de> for MyScalarValue { /// fn deserialize>(de: D) -> Result { /// struct Visitor; /// /// impl<'de> de::Visitor<'de> for Visitor { /// type Value = MyScalarValue; /// /// fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { /// f.write_str("a valid input value") /// } /// /// fn visit_bool(self, b: bool) -> Result { /// Ok(MyScalarValue::Boolean(b)) /// } /// /// fn visit_i32(self, n: i32) -> Result { /// Ok(MyScalarValue::Int(n)) /// } /// /// fn visit_i64(self, n: i64) -> Result { /// if n <= i64::from(i32::MAX) { /// self.visit_i32(n.try_into().unwrap()) /// } else { /// Ok(MyScalarValue::Long(n)) /// } /// } /// /// fn visit_u32(self, n: u32) -> Result { /// if n <= i32::MAX as u32 { /// self.visit_i32(n.try_into().unwrap()) /// } else { /// self.visit_u64(n.into()) /// } /// } /// /// fn visit_u64(self, n: u64) -> Result { /// if n <= i64::MAX as u64 { /// self.visit_i64(n.try_into().unwrap()) /// } else { /// // Browser's `JSON.stringify()` serialize all numbers /// // having no fractional part as integers (no decimal /// // point), so we must parse large integers as floating /// // point, otherwise we would error on transferring large /// // floating point numbers. /// Ok(MyScalarValue::Float(n as f64)) /// } /// } /// /// fn visit_f64(self, f: f64) -> Result { /// Ok(MyScalarValue::Float(f)) /// } /// /// fn visit_str(self, s: &str) -> Result { /// self.visit_string(s.into()) /// } /// /// fn visit_string(self, s: String) -> Result { /// Ok(MyScalarValue::String(s)) /// } /// } /// /// de.deserialize_any(Visitor) /// } /// } /// ``` /// /// [`Deserialize`]: trait@serde::Deserialize /// [`Serialize`]: trait@serde::Serialize pub trait ScalarValue: fmt::Debug + fmt::Display + PartialEq + Clone + DeserializeOwned + Serialize + From + From + From + From + 'static { /// Checks whether this [`ScalarValue`] contains the value of the given /// type. /// /// ``` /// # use juniper::{ScalarValue, DefaultScalarValue}; /// # /// let value = DefaultScalarValue::Int(42); /// /// assert_eq!(value.is_type::(), true); /// assert_eq!(value.is_type::(), false); /// ``` #[must_use] fn is_type<'a, T>(&'a self) -> bool where T: 'a, Option<&'a T>: From<&'a Self>, { >::from(self).is_some() } /// Represents this [`ScalarValue`] as an integer value. /// /// This function is used for implementing [`GraphQLValue`] for [`i32`] for /// all possible [`ScalarValue`]s. Implementations should convert all the /// supported integer types with 32 bit or less to an integer, if requested. /// /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] fn as_int(&self) -> Option; /// Represents this [`ScalarValue`] as a [`String`] value. /// /// This function is used for implementing [`GraphQLValue`] for [`String`] /// for all possible [`ScalarValue`]s. /// /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] fn as_string(&self) -> Option; /// Converts this [`ScalarValue`] into a [`String`] value. /// /// Same as [`ScalarValue::as_string()`], but takes ownership, so allows to /// omit redundant cloning. #[must_use] fn into_string(self) -> Option; /// Represents this [`ScalarValue`] as a [`str`] value. /// /// This function is used for implementing [`GraphQLValue`] for [`str`] for /// all possible [`ScalarValue`]s. /// /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] fn as_str(&self) -> Option<&str>; /// Represents this [`ScalarValue`] as a float value. /// /// This function is used for implementing [`GraphQLValue`] for [`f64`] for /// all possible [`ScalarValue`]s. Implementations should convert all /// supported integer types with 64 bit or less and all floating point /// values with 64 bit or less to a float, if requested. /// /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] fn as_float(&self) -> Option; /// Represents this [`ScalarValue`] as a boolean value /// /// This function is used for implementing [`GraphQLValue`] for [`bool`] for /// all possible [`ScalarValue`]s. /// /// [`GraphQLValue`]: crate::GraphQLValue fn as_bool(&self) -> Option; /// Converts this [`ScalarValue`] into another one. fn into_another(self) -> S { if let Some(i) = self.as_int() { S::from(i) } else if let Some(f) = self.as_float() { S::from(f) } else if let Some(b) = self.as_bool() { S::from(b) } else if let Some(s) = self.into_string() { S::from(s) } else { unreachable!("`ScalarValue` must represent at least one of the GraphQL spec types") } } } /// The default [`ScalarValue`] representation in [`juniper`]. /// /// These types closely follow the [GraphQL specification][0]. /// /// [0]: https://spec.graphql.org/October2021 #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value. /// /// [0]: https://spec.graphql.org/October2021#sec-Int #[value(as_float, as_int)] Int(i32), /// [`Float` scalar][0] as a signed double‐precision fractional values as /// specified by [IEEE 754]. /// /// [0]: https://spec.graphql.org/October2021#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point #[value(as_float)] Float(f64), /// [`String` scalar][0] as a textual data, represented as UTF‐8 character /// sequences. /// /// [0]: https://spec.graphql.org/October2021#sec-String #[value(as_str, as_string, into_string)] String(String), /// [`Boolean` scalar][0] as a `true` or `false` value. /// /// [0]: https://spec.graphql.org/October2021#sec-Boolean #[value(as_bool)] Boolean(bool), } impl<'a> From<&'a str> for DefaultScalarValue { fn from(s: &'a str) -> Self { Self::String(s.into()) } } impl<'a> From> for DefaultScalarValue { fn from(s: Cow<'a, str>) -> Self { Self::String(s.into()) } }