diff --git a/Cargo.lock b/Cargo.lock index bab6e16f6..d96279a2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,13 +90,14 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -114,6 +115,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -635,9 +642,13 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +dependencies = [ + "allocator-api2", + "serde", +] [[package]] name = "bytecheck" @@ -1098,6 +1109,7 @@ dependencies = [ "cynic", "cynic-codegen", "github-schema", + "graphql-query", "insta", "reqwest", "surf", @@ -1132,7 +1144,7 @@ dependencies = [ "indexmap 2.1.0", "insta", "lalrpop-util", - "logos", + "logos 0.14.0", "pretty", "similar-asserts", ] @@ -1682,6 +1694,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "graphql-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb60916f035b5b51d802ddc313e09fb3bd3dd529ea81840dbfabef71e6263e3" +dependencies = [ + "bumpalo", + "hashbrown 0.14.3", + "lexical-core 0.8.5", + "logos 0.12.1", + "serde", + "serde_json", +] + [[package]] name = "h2" version = "0.3.26" @@ -1735,7 +1761,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", ] [[package]] @@ -1743,6 +1769,10 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] [[package]] name = "heck" @@ -2206,6 +2236,70 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.148" @@ -2271,13 +2365,22 @@ dependencies = [ "value-bag", ] +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive 0.12.1", +] + [[package]] name = "logos" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8" dependencies = [ - "logos-derive", + "logos-derive 0.14.0", ] [[package]] @@ -2295,6 +2398,20 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + [[package]] name = "logos-derive" version = "0.14.0" @@ -2404,7 +2521,7 @@ version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" dependencies = [ - "lexical-core", + "lexical-core 0.7.6", "memchr", "version_check", ] @@ -2924,6 +3041,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" @@ -4677,3 +4800,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[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 2.0.66", +] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a65b20b42..04f2fc2e0 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -25,6 +25,9 @@ chrono = { version = "0.4", features = ["serde"] } # have to recompile it as often. github-schema = { path = "../schemas/github" } +# Required for the query inlining example +graphql-query = "1" + [dev-dependencies] insta = "1.17" diff --git a/examples/examples/variable-inlining.rs b/examples/examples/variable-inlining.rs new file mode 100644 index 000000000..f9c66b653 --- /dev/null +++ b/examples/examples/variable-inlining.rs @@ -0,0 +1,142 @@ +//! An example of inlining variables into a GraphQL operation prior to sending +//! +//! This example uses the starwars API but the use case is primarily to support +//! shopifies [bulkOperationRunQuery][1] which requires a document with no variables. +//! +//! [1]: https://shopify.dev/docs/api/admin-graphql/2024-07/mutations/bulkoperationrunquery + +use cynic::queries::{build_executable_document, OperationType}; +use graphql_query::visit::FoldNode; + +// Pull in the Star Wars schema we registered in build.rs +#[cynic::schema("starwars")] +mod schema {} + +#[derive(cynic::QueryFragment, Debug)] +struct Film { + title: Option, + director: Option, +} + +#[derive(cynic::QueryVariables)] +struct InlineVariables { + id: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Root", variables = "InlineVariables")] +struct FilmDirectorQuery { + #[arguments(id: $id)] + film: Option, +} + +fn main() { + match run_query().data { + Some(FilmDirectorQuery { film: Some(film) }) => { + println!("{:?} was directed by {:?}", film.title, film.director) + } + _ => { + println!("No film found"); + } + } +} + +fn run_query() -> cynic::GraphQlResponse { + use cynic::http::ReqwestBlockingExt; + + let query = build_query(); + + reqwest::blocking::Client::new() + .post("https://swapi-graphql.netlify.app/.netlify/functions/index") + .run_graphql(query) + .unwrap() +} + +fn build_query() -> cynic::Operation { + let document = build_executable_document::( + OperationType::Query, + None, + Default::default(), + ); + + let variables = InlineVariables { + id: Some("ZmlsbXM6MQ==".into()), + }; + + use graphql_query::ast::*; + + let ctx = ASTContext::new(); + let ast = Document::parse(&ctx, document).unwrap(); + + let query = ast + .fold(&ctx, &mut VariableInline { variables }) + .unwrap() + .print(); + + cynic::Operation::new(query, ()) +} + +struct VariableInline { + variables: InlineVariables, +} + +impl<'a> graphql_query::visit::Folder<'a> for VariableInline { + fn value( + &mut self, + ctx: &'a graphql_query::ast::ASTContext, + value: graphql_query::ast::Value<'a>, + _info: &graphql_query::visit::VisitInfo, + ) -> graphql_query::visit::Result> { + use graphql_query::ast::{StringValue, Value}; + + let Value::Variable(variable) = value else { + return Ok(value); + }; + + Ok(match variable.name { + "id" => self + .variables + .id + .as_ref() + .map(|id| Value::String(StringValue::new(ctx, id.inner()))) + .unwrap_or(Value::Null), + _ => Value::Null, + }) + } + + fn variable_definitions( + &mut self, + ctx: &'a graphql_query::ast::ASTContext, + _var_defs: graphql_query::ast::VariableDefinitions<'a>, + _info: &graphql_query::visit::VisitInfo, + ) -> graphql_query::visit::Result> { + Ok(graphql_query::ast::VariableDefinitions { + children: graphql_query::bumpalo::vec![in &ctx.arena], + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn snapshot_test_query() { + // Running a snapshot test of the query building functionality as that gives us + // a place to copy and paste the actual GQL we're using for running elsewhere, + // and also helps ensure we don't change queries by mistake + + let query = build_query(); + + insta::assert_snapshot!(query.query); + } + + #[test] + fn test_running_query() { + let result = run_query(); + if result.errors.is_some() { + assert_eq!(result.errors.unwrap().len(), 0); + } + insta::assert_debug_snapshot!(result.data); + } +}