juniper_codegen-0.16.0/.cargo_vcs_info.json0000644000000001550000000000100142610ustar { "git": { "sha1": "63f6f8fae43e9517469d8e316bca382b7e20750a" }, "path_in_vcs": "juniper_codegen" }juniper_codegen-0.16.0/CHANGELOG.md000064400000000000000000000057651046102023000146760ustar 00000000000000`juniper_codegen` changelog =========================== All user visible changes to `juniper_codegen` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. ## [0.16.0] · 2024-03-20 [0.16.0]: /../../tree/juniper_codegen-v0.16.0/juniper_codegen ### BC Breaks - `#[graphql_object]` and `#[graphql_subscription]` expansions now preserve defined `impl` blocks "as is" and reuse defined methods in opaque way. ([#971], [#1245]) - Renamed `rename = ""` attribute argument to `rename_all = ""` (following `serde` style). ([#971]) - 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]. ### Added - `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026]) ### Changed - Migrated to 2 version of `syn` crate. ([#1157]) ### Fixed - All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) [#113]: /../../issues/113 [#971]: /../../pull/971 [#985]: /../../pull/985 [#987]: /../../pull/987 [#1000]: /../../issues/1000 [#1003]: /../../pull/1003 [#1009]: /../../pull/1009 [#1014]: /../../pull/1014 [#1017]: /../../pull/1017 [#1025]: /../../pull/1025 [#1026]: /../../pull/1026 [#1028]: /../../pull/1028 [#1051]: /../../issues/1051 [#1054]: /../../pull/1054 [#1157]: /../../pull/1157 [#1245]: /../../pull/1245 [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules [Semantic Versioning 2.0.0]: https://semver.org juniper_codegen-0.16.0/Cargo.toml0000644000000030200000000000100122510ustar # 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_codegen" version = "0.16.0" authors = [ "Magnus Hallin ", "Christoph Herzog ", "Ilya Solovyiov ", "Kai Ren ", ] exclude = ["/release.toml"] description = "Code generation for `juniper` crate." homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_codegen" documentation = "https://docs.rs/juniper-codegen" readme = "README.md" keywords = [ "codegen", "graphql", "juniper", "macros", ] license = "BSD-2-Clause" repository = "https://github.com/graphql-rust/juniper" resolver = "1" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.4" [dependencies.quote] version = "1.0.9" [dependencies.syn] version = "2.0" features = [ "extra-traits", "full", "visit", "visit-mut", ] [dependencies.url] version = "2.0" [dev-dependencies.derive_more] version = "0.99.8" [dev-dependencies.futures] version = "0.3.22" [dev-dependencies.serde] version = "1.0.122" juniper_codegen-0.16.0/Cargo.toml.orig000064400000000000000000000016511046102023000157420ustar 00000000000000[package] name = "juniper_codegen" version = "0.16.0" edition = "2021" rust-version = "1.73" description = "Code generation for `juniper` crate." license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", "Ilya Solovyiov ", "Kai Ren ", ] documentation = "https://docs.rs/juniper-codegen" homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_codegen" repository = "https://github.com/graphql-rust/juniper" readme = "README.md" keywords = ["codegen", "graphql", "juniper", "macros"] exclude = ["/release.toml"] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.4" quote = "1.0.9" syn = { version = "2.0", features = ["extra-traits", "full", "visit", "visit-mut"] } url = "2.0" [dev-dependencies] derive_more = "0.99.8" futures = "0.3.22" juniper = { path = "../juniper" } serde = "1.0.122" juniper_codegen-0.16.0/LICENSE000064400000000000000000000027731046102023000140660ustar 00000000000000BSD 2-Clause License Copyright (c) 2016-2024 Magnus Hallin , Christoph Herzog , 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_codegen-0.16.0/README.md000064400000000000000000000017521046102023000143340ustar 00000000000000`juniper_codegen` crate ======================= [![Crates.io](https://img.shields.io/crates/v/juniper_codegen.svg?maxAge=2592000)](https://crates.io/crates/juniper_codegen) [![Documentation](https://docs.rs/juniper_codegen/badge.svg)](https://docs.rs/juniper_codegen) [![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) - [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_codegen-v0.16.0/juniper_codegen/CHANGELOG.md) Code generation for [`juniper`] crate. DO NOT use it directly, use [`juniper`] crate instead. ## License This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/juniper_codegen-v0.16.0/juniper_codegen/LICENSE). [`juniper`]: https://docs.rs/juniper juniper_codegen-0.16.0/src/common/default.rs000064400000000000000000000031071046102023000171220ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL default values][0] //! //! [0]: https://spec.graphql.org/October2021#DefaultValue use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, token, }; use crate::common::parse::ParseBufferExt as _; /// Representation of a [GraphQL default value][0] for code generation. /// /// [0]: https://spec.graphql.org/October2021#DefaultValue #[derive(Clone, Debug, Default)] pub(crate) enum Value { /// [`Default`] implementation should be used. #[default] Default, /// Explicit [`Expr`]ession to be used as the [default value][0]. /// /// [`Expr`]: syn::Expr /// [0]: https://spec.graphql.org/October2021#DefaultValue Expr(Box), } impl From> for Value { fn from(opt: Option) -> Self { match opt { Some(expr) => Self::Expr(Box::new(expr)), None => Self::Default, } } } impl Parse for Value { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(input .try_parse::()? .map(|_| input.parse::()) .transpose()? .into()) } } impl ToTokens for Value { fn to_tokens(&self, into: &mut TokenStream) { match self { Self::Default => quote! { ::std::default::Default::default() }, Self::Expr(expr) => quote! { (#expr).into() }, } .to_tokens(into) } } juniper_codegen-0.16.0/src/common/deprecation.rs000064400000000000000000000122141046102023000177720ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL deprecation directive][0]. //! //! [0]: https://spec.graphql.org/October2021#sec--deprecated use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned as _, token, }; use crate::common::{parse::ParseBufferExt as _, SpanContainer}; /// [GraphQL deprecation directive][0] defined on a [GraphQL field][1] or a /// [GraphQL enum value][2] via `#[graphql(deprecated = ...)]` (or /// `#[deprecated(note = ...)]`) attribute. /// /// [0]: https://spec.graphql.org/October2021#sec--deprecated /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Enum-Value #[derive(Debug, Default, Eq, PartialEq)] pub(crate) struct Directive { /// Optional [reason][1] attached to this [deprecation][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--deprecated /// [1]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L pub(crate) reason: Option, } impl Parse for Directive { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { reason: input .try_parse::()? .map(|_| input.parse::()) .transpose()?, }) } } impl Directive { /// Tries to parse a [`Directive`] from a `#[deprecated(note = ...)]` /// attribute, by looking up for it in the provided [`syn::Attribute`]s. /// /// # Errors /// /// If failed to parse a [`Directive`] from a found /// `#[deprecated(note = ...)]` attribute. pub(crate) fn parse_from_deprecated_attr( attrs: &[syn::Attribute], ) -> syn::Result>> { for attr in attrs { return Ok(match &attr.meta { syn::Meta::List(list) if list.path.is_ident("deprecated") => { let directive = Self::parse_from_deprecated_meta_list(list)?; Some(SpanContainer::new( list.path.span(), directive.reason.as_ref().map(|r| r.span()), directive, )) } syn::Meta::Path(path) if path.is_ident("deprecated") => { Some(SpanContainer::new(path.span(), None, Self::default())) } _ => continue, }); } Ok(None) } /// Tries to parse a [`Directive`] from the [`syn::MetaList`] of a single /// `#[deprecated(note = ...)]` attribute. /// /// # Errors /// /// If the `#[deprecated(note = ...)]` attribute has incorrect format. fn parse_from_deprecated_meta_list(list: &syn::MetaList) -> syn::Result { for meta in list.parse_args_with(Punctuated::::parse_terminated)? { if let syn::Meta::NameValue(nv) = meta { return if !nv.path.is_ident("note") { Err(syn::Error::new( nv.path.span(), "unrecognized setting on #[deprecated(..)] attribute", )) } else if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(strlit), .. }) = &nv.value { Ok(Self { reason: Some(strlit.clone()), }) } else { Err(syn::Error::new( nv.value.span(), "only strings are allowed for deprecation", )) }; } } Ok(Self::default()) } } impl ToTokens for Directive { fn to_tokens(&self, into: &mut TokenStream) { let reason = self .reason .as_ref() .map_or_else(|| quote! { None }, |text| quote! { Some(#text) }); quote! { .deprecated(::core::option::Option::#reason) } .to_tokens(into); } } #[cfg(test)] mod parse_from_deprecated_attr_test { use quote::quote; use syn::parse_quote; use super::Directive; #[test] fn single() { let desc = Directive::parse_from_deprecated_attr(&[parse_quote! { #[deprecated(note = "foo")] }]) .unwrap() .unwrap() .into_inner(); assert_eq!( quote! { #desc }.to_string(), quote! { .deprecated(::core::option::Option::Some("foo")) }.to_string(), ); } #[test] fn no_reason() { let desc = Directive::parse_from_deprecated_attr(&[parse_quote! { #[deprecated] }]) .unwrap() .unwrap() .into_inner(); assert_eq!( quote! { #desc }.to_string(), quote! { .deprecated(::core::option::Option::None) }.to_string(), ); } #[test] fn not_deprecation() { let desc = Directive::parse_from_deprecated_attr(&[parse_quote! { #[blah = "foo"] }]).unwrap(); assert_eq!(desc, None); } } juniper_codegen-0.16.0/src/common/description.rs000064400000000000000000000134001046102023000200160ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL description][0]. //! //! [0]: https://spec.graphql.org/October2021#sec-Descriptions use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned as _, }; use crate::common::SpanContainer; /// [GraphQL description][0] defined on a GraphQL definition via /// `#[graphql(description = ...)]` (or `#[doc = ...]`) attribute. /// /// [0]: https://spec.graphql.org/October2021#sec-Descriptions #[derive(Debug, Eq, PartialEq)] pub(crate) struct Description(syn::LitStr); impl Parse for Description { fn parse(input: ParseStream<'_>) -> syn::Result { input.parse::().map(Self) } } impl Description { /// Tries to parse a [`Description`] from a `#[doc = ...]` attribute (or /// Rust doc comment), by looking up for it in the provided /// [`syn::Attribute`]s. /// /// # Errors /// /// If failed to parse a [`Description`] from a found `#[doc = ...]` /// attribute. pub(crate) fn parse_from_doc_attrs( attrs: &[syn::Attribute], ) -> syn::Result>> { let (mut first_span, mut descriptions) = (None, Vec::new()); for attr in attrs { match attr.meta { syn::Meta::NameValue(ref nv) if nv.path.is_ident("doc") => { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(strlit), .. }) = &nv.value { if first_span.is_none() { first_span = Some(strlit.span()); } descriptions.push(strlit.value()); } else { return Err(syn::Error::new( nv.value.span(), "#[doc] attributes may only have a string literal", )); } } _ => continue, } } Ok(first_span.map(|span| { SpanContainer::new( span, None, Self(syn::LitStr::new(&Self::concatenate(&descriptions), span)), ) })) } /// Concatenates [`Description`] strings into a single one. fn concatenate(descriptions: &[String]) -> String { let last_index = descriptions.len() - 1; descriptions .iter() .map(|s| s.as_str().trim_end()) .map(|s| { // Trim leading space. s.strip_prefix(' ').unwrap_or(s) }) .enumerate() .fold(String::new(), |mut buffer, (index, s)| { // Add newline, except when string ends in a continuation // backslash or is the last line. if index == last_index { buffer.push_str(s); } else if s.ends_with('\\') { buffer.push_str(s.trim_end_matches('\\')); buffer.push(' '); } else { buffer.push_str(s); buffer.push('\n'); } buffer }) } } impl ToTokens for Description { fn to_tokens(&self, into: &mut TokenStream) { let desc = &self.0; quote! { .description(#desc) } .to_tokens(into); } } #[cfg(test)] mod parse_from_doc_attrs_test { use quote::quote; use syn::parse_quote; use super::Description; #[test] fn single() { let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[doc = "foo"] }]) .unwrap() .unwrap() .into_inner(); assert_eq!( quote! { #desc }.to_string(), quote! { .description("foo") }.to_string(), ); } #[test] fn many() { let desc = Description::parse_from_doc_attrs(&[ parse_quote! { #[doc = "foo"] }, parse_quote! { #[doc = "\n"] }, parse_quote! { #[doc = "bar"] }, ]) .unwrap() .unwrap() .into_inner(); assert_eq!( quote! { #desc }.to_string(), quote! { .description("foo\n\nbar") }.to_string(), ); } #[test] fn not_doc() { let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[blah = "foo"] }]).unwrap(); assert_eq!(desc, None); } } #[cfg(test)] mod concatenate_test { use super::Description; /// Forms a [`Vec`] of [`String`]s out of the provided [`str`]s /// [`Iterator`]. fn to_strings<'i>(source: impl IntoIterator) -> Vec { source.into_iter().map(Into::into).collect() } #[test] fn single() { assert_eq!(Description::concatenate(&to_strings(["foo"])), "foo"); } #[test] fn multiple() { assert_eq!( Description::concatenate(&to_strings(["foo", "bar"])), "foo\nbar", ); } #[test] fn trims_spaces() { assert_eq!( Description::concatenate(&to_strings([" foo ", "bar ", " baz"])), "foo\nbar\nbaz", ); } #[test] fn empty() { assert_eq!( Description::concatenate(&to_strings(["foo", "", "bar"])), "foo\n\nbar", ); } #[test] fn newline_spaces() { assert_eq!( Description::concatenate(&to_strings(["foo ", "", " bar"])), "foo\n\nbar", ); } #[test] fn continuation_backslash() { assert_eq!( Description::concatenate(&to_strings(["foo\\", "x\\", "y", "bar"])), "foo x y\nbar", ); } } juniper_codegen-0.16.0/src/common/diagnostic.rs000064400000000000000000000277561046102023000176420ustar 00000000000000use std::fmt; use proc_macro2::Span; pub(crate) use self::polyfill::{ abort_if_dirty, emit_error, entry_point, entry_point_with_preserved_body, Diagnostic, ResultExt, }; /// URL of the GraphQL specification (October 2021 Edition). pub(crate) const SPEC_URL: &str = "https://spec.graphql.org/October2021"; pub(crate) enum Scope { EnumDerive, InputObjectDerive, InterfaceAttr, InterfaceDerive, ObjectAttr, ObjectDerive, ScalarAttr, ScalarDerive, ScalarValueDerive, UnionAttr, UnionDerive, } impl Scope { pub(crate) fn spec_section(&self) -> &str { match self { Self::EnumDerive => "#sec-Enums", Self::InputObjectDerive => "#sec-Input-Objects", Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", } } } impl fmt::Display for Scope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { Self::EnumDerive => "enum", Self::InputObjectDerive => "input object", Self::InterfaceAttr | Self::InterfaceDerive => "interface", Self::ObjectAttr | Self::ObjectDerive => "object", Self::ScalarAttr | Self::ScalarDerive => "scalar", Self::ScalarValueDerive => "built-in scalars", Self::UnionAttr | Self::UnionDerive => "union", }; write!(f, "GraphQL {name}") } } impl Scope { fn spec_link(&self) -> String { format!("{SPEC_URL}{}", self.spec_section()) } pub(crate) fn custom>(&self, span: Span, msg: S) -> Diagnostic { Diagnostic::spanned(span, format!("{self} {}", msg.as_ref())).note(self.spec_link()) } pub(crate) fn error(&self, err: &syn::Error) -> Diagnostic { Diagnostic::spanned(err.span(), format!("{self} {err}")).note(self.spec_link()) } pub(crate) fn emit_custom>(&self, span: Span, msg: S) { self.custom(span, msg).emit() } pub(crate) fn custom_error>(&self, span: Span, msg: S) -> syn::Error { syn::Error::new(span, format!("{self} {}", msg.as_ref())) } pub(crate) fn no_double_underscore(&self, field: Span) { Diagnostic::spanned( field, "All types and directives defined within a schema must not have a name which begins \ with `__` (two underscores), as this is used exclusively by GraphQL’s introspection \ system.", ) .note(format!("{SPEC_URL}#sec-Schema")) .emit(); } } mod polyfill { //! Simplified version of [`proc_macro_error`] machinery for this crate purposes. //! //! [`proc_macro_error`]: https://docs.rs/proc-macro-error/1 use std::{ cell::{Cell, RefCell}, panic::{catch_unwind, resume_unwind, UnwindSafe}, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; /// Representation of a single diagnostic message. #[derive(Debug)] pub(crate) struct Diagnostic { span_range: SpanRange, msg: String, suggestions: Vec, } impl Diagnostic { /// Create a new [`Diagnostic`] message that points to the provided [`Span`]. pub(crate) fn spanned(span: Span, message: impl Into) -> Self { Self { span_range: SpanRange { first: span, last: span, }, msg: message.into(), suggestions: vec![], } } /// Attaches a note to the main message of this [`Diagnostic`]. pub(crate) fn note(mut self, msg: impl Into) -> Self { self.suggestions.push(msg.into()); self } /// Aborts macro execution and display this [`Diagnostic`]. pub(crate) fn abort(self) -> ! { self.emit(); abort_now() } /// Display this [`Diagnostic`] while not aborting macro execution. pub(crate) fn emit(self) { check_correctness(); emit_diagnostic(self); } } impl ToTokens for Diagnostic { fn to_tokens(&self, ts: &mut TokenStream) { use std::borrow::Cow; fn ensure_lf(buf: &mut String, s: &str) { if s.ends_with('\n') { buf.push_str(s); } else { buf.push_str(s); buf.push('\n'); } } fn diag_to_tokens( span_range: SpanRange, msg: &str, suggestions: &[String], ) -> TokenStream { let message = if suggestions.is_empty() { Cow::Borrowed(msg) } else { let mut message = String::new(); ensure_lf(&mut message, msg); for note in suggestions { message.push_str("· note: "); ensure_lf(&mut message, note); } Cow::Owned(message) }; let mut msg = proc_macro2::Literal::string(&message); msg.set_span(span_range.last); let group = quote_spanned!(span_range.last=> { #msg } ); quote_spanned!(span_range.first=> compile_error!#group) } ts.extend(diag_to_tokens( self.span_range, &self.msg, self.suggestions.as_ref(), )); } } impl From for Diagnostic { fn from(err: syn::Error) -> Self { use proc_macro2::{Delimiter, TokenTree}; fn gut_error(ts: &mut impl Iterator) -> Option<(SpanRange, String)> { let first = ts.next()?.span(); // : assert_eq!(ts.next().unwrap().to_string(), ":"); assert_eq!(ts.next().unwrap().to_string(), "core"); assert_eq!(ts.next().unwrap().to_string(), ":"); assert_eq!(ts.next().unwrap().to_string(), ":"); assert_eq!(ts.next().unwrap().to_string(), "compile_error"); assert_eq!(ts.next().unwrap().to_string(), "!"); let lit = match ts.next().unwrap() { TokenTree::Group(group) => { // Currently `syn` builds `compile_error!` invocations // exclusively in `ident{"..."}` (braced) form which is not // followed by `;` (semicolon). // // But if it changes to `ident("...");` (parenthesized) // or `ident["..."];` (bracketed) form, // we will need to skip the `;` as well. // Highly unlikely, but better safe than sorry. if group.delimiter() == Delimiter::Parenthesis || group.delimiter() == Delimiter::Bracket { ts.next().unwrap(); // ; } match group.stream().into_iter().next().unwrap() { TokenTree::Literal(lit) => lit, tt => unreachable!("Diagnostic::gut_error(): TokenTree::Group: {tt}"), } } tt => unreachable!("Diagnostic::gut_error(): {tt}"), }; let last = lit.span(); let mut msg = lit.to_string(); // "abc" => abc msg.pop(); msg.remove(0); Some((SpanRange { first, last }, msg)) } let mut ts = err.to_compile_error().into_iter(); let (span_range, msg) = gut_error(&mut ts).unwrap(); Self { span_range, msg, suggestions: vec![], } } } /// Emits a [`syn::Error`] while not aborting macro execution. pub(crate) fn emit_error(e: syn::Error) { Diagnostic::from(e).emit() } /// Range of [`Span`]s. #[derive(Clone, Copy, Debug)] struct SpanRange { first: Span, last: Span, } thread_local! { static ENTERED_ENTRY_POINT: Cell = Cell::new(0); } /// This is the entry point for a macro to support [`Diagnostic`]s. pub(crate) fn entry_point(f: F) -> proc_macro::TokenStream where F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, { entry_point_with_preserved_body(TokenStream::new(), f) } /// This is the entry point for an attribute macro to support [`Diagnostic`]s, while preserving /// the `body` input [`proc_macro::TokenStream`] on errors. pub(crate) fn entry_point_with_preserved_body( body: impl Into, f: F, ) -> proc_macro::TokenStream where F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, { ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() + 1)); let caught = catch_unwind(f); let err_storage = ERR_STORAGE.with(|s| s.replace(Vec::new())); ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() - 1)); let gen_error = || { let body = body.into(); quote! { #body #( #err_storage )* } }; match caught { Ok(ts) => { if err_storage.is_empty() { ts } else { gen_error().into() } } Err(boxed) => match boxed.downcast_ref::<&str>() { Some(p) if *p == "diagnostic::polyfill::abort_now" => gen_error().into(), _ => resume_unwind(boxed), }, } } fn check_correctness() { if ENTERED_ENTRY_POINT.get() == 0 { panic!( "`common::diagnostic` API cannot be used outside of `entry_point()` invocation, \ perhaps you forgot to invoke it your #[proc_macro] function", ); } } thread_local! { static ERR_STORAGE: RefCell> = RefCell::new(Vec::new()); } /// Emits the provided [`Diagnostic`], while not aborting macro execution. fn emit_diagnostic(diag: Diagnostic) { ERR_STORAGE.with(|s| s.borrow_mut().push(diag)); } /// Aborts macro execution. if any [`Diagnostic`]s were emitted before. pub(crate) fn abort_if_dirty() { check_correctness(); ERR_STORAGE.with(|s| { if !s.borrow().is_empty() { abort_now() } }); } fn abort_now() -> ! { check_correctness(); panic!("diagnostic::polyfill::abort_now") } /// Extension of `Result>` with some handy shortcuts. pub(crate) trait ResultExt { type Ok; /// Behaves like [`Result::unwrap()`]: if `self` is [`Ok`] yield the contained value, /// otherwise abort macro execution. fn unwrap_or_abort(self) -> Self::Ok; /// Behaves like [`Result::expect()`]: if `self` is [`Ok`] yield the contained value, /// otherwise abort macro execution. /// /// If it aborts then resulting error message will be preceded with the provided `message`. fn expect_or_abort(self, message: &str) -> Self::Ok; } impl> ResultExt for Result { type Ok = T; fn unwrap_or_abort(self) -> T { self.unwrap_or_else(|e| e.into().abort()) } fn expect_or_abort(self, message: &str) -> T { self.unwrap_or_else(|e| { let mut d = e.into(); d.msg = format!("{message}: {}", d.msg); d.abort() }) } } } juniper_codegen-0.16.0/src/common/field/arg.rs000064400000000000000000000400051046102023000173300ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL arguments][1] //! //! [1]: https://spec.graphql.org/October2021#sec-Language.Arguments. use std::mem; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, spanned::Spanned, token, }; use crate::common::{ default, diagnostic, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, TypeExt as _, }, path_eq_single, rename, scalar, Description, SpanContainer, }; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a /// method argument, when generating code for [GraphQL argument][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments #[derive(Debug, Default)] pub(crate) struct Attr { /// Explicitly specified name of a [GraphQL argument][1] represented by this /// method argument. /// /// If [`None`], then `camelCased` Rust argument name is used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL argument][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option>, /// Explicitly specified [default value][2] of this [GraphQL argument][1]. /// /// If [`None`], then this [GraphQL argument][1] is considered as /// [required][2]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments pub(crate) default: Option>, /// Explicitly specified marker indicating that this method argument doesn't /// represent a [GraphQL argument][1], but is a [`Context`] being injected /// into a [GraphQL field][2] resolving function. /// /// If absent, then the method argument still is considered as [`Context`] /// if it's named `context` or `ctx`. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) context: Option>, /// Explicitly specified marker indicating that this method argument doesn't /// represent a [GraphQL argument][1], but is an [`Executor`] being injected /// into a [GraphQL field][2] resolving function. /// /// If absent, then the method argument still is considered as [`Executor`] /// if it's named `executor`. /// /// [`Executor`]: juniper::Executor /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) executor: Option>, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "default" => { let val = input.parse::()?; out.default .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { let span = ident.span(); out.context .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))? } "exec" | "executor" => { let span = ident.span(); out.executor .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))? } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), default: try_merge_opt!(default: self, another), context: try_merge_opt!(context: self, another), executor: try_merge_opt!(executor: self, another), }) } /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a function argument. pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(context) = &attr.context { if attr.name.is_some() || attr.description.is_some() || attr.default.is_some() || attr.executor.is_some() { return Err(syn::Error::new( context.span(), "`context` attribute argument is not composable with any other arguments", )); } } if let Some(executor) = &attr.executor { if attr.name.is_some() || attr.description.is_some() || attr.default.is_some() || attr.context.is_some() { return Err(syn::Error::new( executor.span(), "`executor` attribute argument is not composable with any other arguments", )); } } Ok(attr) } /// Checks whether this [`Attr`] doesn't contain arguments related to an /// [`OnField`] argument. fn ensure_no_regular_arguments(&self) -> syn::Result<()> { if let Some(span) = &self.name { return Err(Self::err_disallowed(&span, "name")); } if let Some(span) = &self.description { return Err(Self::err_disallowed(&span, "description")); } if let Some(span) = &self.default { return Err(Self::err_disallowed(&span, "default")); } Ok(()) } /// Emits "argument is not allowed" [`syn::Error`] for the given `arg` /// pointing to the given `span`. #[must_use] fn err_disallowed(span: &S, arg: &str) -> syn::Error { syn::Error::new( span.span(), format!("attribute argument `#[graphql({arg} = ...)]` is not allowed here",), ) } } /// Representation of a [GraphQL field argument][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments #[derive(Debug)] pub(crate) struct OnField { /// Rust type that this [GraphQL field argument][1] is represented by. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) ty: syn::Type, /// Name of this [GraphQL field argument][2] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub(crate) name: String, /// [Description][2] of this [GraphQL field argument][1] to put into GraphQL /// schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option, /// Default value of this [GraphQL field argument][1] in GraphQL schema. /// /// If [`None`], then this [argument][1] is a [required][2] one. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments pub(crate) default: Option, } /// Possible kinds of Rust method arguments for code generation. #[derive(Debug)] pub(crate) enum OnMethod { /// Regular [GraphQL field argument][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments Regular(Box), /// [`Context`] passed into a [GraphQL field][2] resolving method. /// /// [`Context`]: juniper::Context /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields Context(Box), /// [`Executor`] passed into a [GraphQL field][2] resolving method. /// /// [`Executor`]: juniper::Executor /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields Executor, } impl OnMethod { /// Returns this argument as the one [`OnField`], if it represents the one. #[must_use] pub(crate) fn as_regular(&self) -> Option<&OnField> { if let Self::Regular(arg) = self { Some(&**arg) } else { None } } /// Returns [`syn::Type`] of this [`OnMethod::Context`], if it represents /// the one. #[must_use] pub(crate) fn context_ty(&self) -> Option<&syn::Type> { if let Self::Context(ty) = self { Some(&**ty) } else { None } } /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this argument, if it represents an /// [`OnField`] one. /// /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark #[must_use] pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option { let ty = &self.as_regular()?.ty; Some(quote_spanned! { ty.span() => <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) } /// Returns generated code for the [`GraphQLType::meta`] method, which /// registers this argument in [`Registry`], if it represents an [`OnField`] /// argument. /// /// [`GraphQLType::meta`]: juniper::GraphQLType::meta /// [`Registry`]: juniper::Registry #[must_use] pub(crate) fn method_meta_tokens(&self) -> Option { let arg = self.as_regular()?; let (name, ty) = (&arg.name, &arg.ty); let description = &arg.description; let method = if let Some(val) = &arg.default { quote_spanned! { val.span() => .arg_with_default::<#ty>(#name, &#val, info) } } else { quote! { .arg::<#ty>(#name, info) } }; Some(quote! { .argument(registry #method #description) }) } /// Returns generated code for the [`GraphQLValue::resolve_field`] method, /// which provides the value of this [`OnMethod`] argument to be passed into /// a trait method call. /// /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field #[must_use] pub(crate) fn method_resolve_field_tokens( &self, scalar: &scalar::Type, for_async: bool, ) -> TokenStream { match self { Self::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); let err_text = format!("Missing argument `{name}`: {{}}"); let arg = quote! { args.get::<#ty>(#name).and_then(|opt| opt.map_or_else(|| { <#ty as ::juniper::FromInputValue<#scalar>>::from_implicit_null() .map_err(|e| { ::juniper::IntoFieldError::<#scalar>::into_field_error(e) .map_message(|m| format!(#err_text, m)) }) }, ::core::result::Result::Ok)) }; if for_async { quote! { match #arg { ::core::result::Result::Ok(v) => v, ::core::result::Result::Err(e) => return ::std::boxed::Box::pin(async { ::core::result::Result::Err(e) }), } } } else { quote! { #arg? } } } Self::Context(_) => quote! { ::juniper::FromContext::from(executor.context()) }, Self::Executor => quote! { &executor }, } } /// Parses an [`OnMethod`] argument from the given Rust method argument /// definition. /// /// Returns [`None`] if parsing fails and emits parsing errors into the /// given `scope`. pub(crate) fn parse( argument: &mut syn::PatType, renaming: &rename::Policy, scope: &diagnostic::Scope, ) -> Option { let orig_attrs = argument.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. argument.attrs = mem::take(&mut argument.attrs) .into_iter() .filter(|attr| !path_eq_single(attr.path(), "graphql")) .collect(); let attr = Attr::from_attrs("graphql", &orig_attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.context.is_some() { return Some(Self::Context(Box::new(argument.ty.unreferenced().clone()))); } if attr.executor.is_some() { return Some(Self::Executor); } if let syn::Pat::Ident(name) = &*argument.pat { let arg = match name.ident.unraw().to_string().as_str() { "context" | "ctx" | "_context" | "_ctx" => { Some(Self::Context(Box::new(argument.ty.unreferenced().clone()))) } "executor" | "_executor" => Some(Self::Executor), _ => None, }; if arg.is_some() { attr.ensure_no_regular_arguments() .map_err(|e| scope.error(&e).emit()) .ok()?; return arg; } } let name = if let Some(name) = attr.name.as_ref() { name.as_ref().value() } else if let syn::Pat::Ident(name) = &*argument.pat { renaming.apply(&name.ident.unraw().to_string()) } else { scope .custom( argument.pat.span(), "method argument should be declared as a single identifier", ) .note(String::from( "use `#[graphql(name = ...)]` attribute to specify custom argument's \ name without requiring it being a single identifier", )) .emit(); return None; }; if name.starts_with("__") { scope.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| argument.pat.span()), ); return None; } Some(Self::Regular(Box::new(OnField { name, ty: argument.ty.as_ref().clone(), description: attr.description.map(SpanContainer::into_inner), default: attr.default.map(SpanContainer::into_inner), }))) } } juniper_codegen-0.16.0/src/common/field/mod.rs000064400000000000000000000347651046102023000173560ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of [GraphQL fields][1] //! //! [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) mod arg; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, }; use crate::common::{ deprecation, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, scalar, Description, SpanContainer, }; pub(crate) use self::arg::OnMethod as MethodArgument; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a /// [GraphQL field][1] definition. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[derive(Debug, Default)] pub(crate) struct Attr { /// Explicitly specified name of this [GraphQL field][1]. /// /// If [`None`], then `camelCased` Rust method name is used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL field][1]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option>, /// Explicitly specified [deprecation][2] of this [GraphQL field][1]. /// /// If [`None`], then Rust `#[deprecated]` attribute will be used as the /// [deprecation][2], if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Deprecation pub(crate) deprecated: Option>, /// Explicitly specified marker indicating that this method (or struct /// field) should be omitted by code generation and not considered as the /// [GraphQL field][1] definition. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ignore: Option>, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "deprecated" => { let directive = input.parse::()?; out.deprecated .replace(SpanContainer::new( ident.span(), directive.reason.as_ref().map(|r| r.span()), directive, )) .none_or_else(|_| err::dup_arg(&ident))? } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attrs`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), }) } /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a [GraphQL field][1] definition. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(ignore) = &attr.ignore { if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() { return Err(syn::Error::new( ignore.span(), "`ignore` attribute argument is not composable with any other arguments", )); } } if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } if attr.deprecated.is_none() { attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; } Ok(attr) } } /// Representation of a [GraphQL field][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[derive(Debug)] pub(crate) struct Definition { /// Rust type that this [GraphQL field][1] is represented by (method return /// type or struct field type). /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ty: syn::Type, /// Name of this [GraphQL field][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) name: String, /// [Description][2] of this [GraphQL field][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option, /// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Deprecation pub(crate) deprecated: Option, /// Ident of the Rust method (or struct field) representing this /// [GraphQL field][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) ident: syn::Ident, /// Rust [`MethodArgument`]s required to call the method representing this /// [GraphQL field][1]. /// /// If [`None`] then this [GraphQL field][1] is represented by a struct /// field. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) arguments: Option>, /// Indicator whether the Rust method representing this [GraphQL field][1] /// has a [`syn::Receiver`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) has_receiver: bool, /// Indicator whether this [GraphQL field][1] should be resolved /// asynchronously. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) is_async: bool, } impl Definition { /// Indicates whether this [GraphQL field][1] is represented by a method, /// not a struct field. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn is_method(&self) -> bool { self.arguments.is_some() } /// Returns generated code that errors about unknown [GraphQL field][1] /// tried to be resolved in the [`GraphQLValue::resolve_field`] method. /// /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_resolve_field_err_no_field_tokens( scalar: &scalar::Type, ty_name: &str, ) -> TokenStream { quote! { return ::core::result::Result::Err(::juniper::FieldError::from(::std::format!( "Field `{}` not found on type `{}`", field, >::name(info) .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?, ))) } } /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this [GraphQL field][1]. /// /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_mark_tokens( &self, infer_result: bool, scalar: &scalar::Type, ) -> TokenStream { let args_marks = self .arguments .iter() .flat_map(|args| args.iter().filter_map(|a| a.method_mark_tokens(scalar))); let ty = &self.ty; let mut ty = quote! { #ty }; if infer_result { ty = quote! { <#ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item }; } let resolved_ty = quote! { <#ty as ::juniper::IntoResolvable< '_, #scalar, _, >::Context, >>::Type }; quote_spanned! { self.ty.span() => #( #args_marks )* <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } } /// Returns generated code for the [`GraphQLType::meta`] method, which /// registers this [GraphQL field][1] in [`Registry`]. /// /// [`GraphQLType::meta`]: juniper::GraphQLType::meta /// [`Registry`]: juniper::Registry /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn method_meta_tokens( &self, extract_stream_type: Option<&scalar::Type>, ) -> TokenStream { let (name, ty) = (&self.name, &self.ty); let mut ty = quote! { #ty }; if let Some(scalar) = extract_stream_type { ty = quote! { <#ty as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item }; } let description = &self.description; let deprecated = &self.deprecated; let args = self .arguments .iter() .flat_map(|args| args.iter().filter_map(MethodArgument::method_meta_tokens)); quote! { registry.field_convert::<#ty, _, Self::Context>(#name, info) #( #args )* #description #deprecated } } /// Returns generated code for the /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which /// resolves this [GraphQL field][1] as [subscription][2]. /// /// [0]: juniper::GraphQLSubscriptionValue::resolve_field_into_stream /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] pub(crate) fn method_resolve_field_into_stream_tokens( &self, scalar: &scalar::Type, ) -> TokenStream { let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); let mut fut = if self.is_method() { let args = self .arguments .as_ref() .unwrap() .iter() .map(|arg| arg.method_resolve_field_tokens(scalar, false)); let rcv = self.has_receiver.then(|| { quote! { self, } }); quote! { Self::#ident(#rcv #( #args ),*) } } else { ty = parse_quote! { _ }; quote! { &self.#ident } }; if !self.is_async { fut = quote! { ::juniper::futures::future::ready(#fut) }; } quote! { #name => { ::juniper::futures::FutureExt::boxed(async move { let res: #ty = #fut.await; let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?; let executor = executor.as_owned_executor(); let stream = ::juniper::futures::StreamExt::then(res, move |res| { let executor = executor.clone(); let res2: ::juniper::FieldResult<_, #scalar> = ::juniper::IntoResolvable::into_resolvable(res, executor.context()); async move { let ex = executor.as_executor(); match res2 { ::core::result::Result::Ok( ::core::option::Option::Some((ctx, r)), ) => { let sub = ex.replaced_context(ctx); sub.resolve_with_ctx_async(&(), &r) .await .map_err(|e| ex.new_error(e)) } ::core::result::Result::Ok(::core::option::Option::None) => { ::core::result::Result::Ok(::juniper::Value::null()) } ::core::result::Result::Err(e) => { ::core::result::Result::Err(ex.new_error(e)) } } } }); ::core::result::Result::Ok(::juniper::Value::Scalar::< ::juniper::ValuesStream::<#scalar> >(::juniper::futures::StreamExt::boxed(stream))) }) } } } } /// Checks whether all [GraphQL fields][1] fields have different names. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields #[must_use] pub(crate) fn all_different(fields: &[Definition]) -> bool { let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect(); names.dedup(); names.len() == fields.len() } juniper_codegen-0.16.0/src/common/gen.rs000064400000000000000000000041131046102023000162450ustar 00000000000000//! Common code generated parts, used by this crate. use proc_macro2::TokenStream; use quote::quote; /// Generate the code resolving some [GraphQL type][1] in a synchronous manner. /// /// Value of a [GraphQL type][1] should be stored in a `res` binding in the generated code, before /// including this piece of code. /// /// [1]: https://spec.graphql.org/October2021#sec-Types pub(crate) fn sync_resolving_code() -> TokenStream { quote! { ::juniper::IntoResolvable::into_resolvable(res, executor.context()) .and_then(|res| match res { ::core::option::Option::Some((ctx, r)) => { executor.replaced_context(ctx).resolve_with_ctx(info, &r) } ::core::option::Option::None => { ::core::result::Result::Ok(::juniper::Value::null()) } }) } } /// Generate the code resolving some [GraphQL type][1] in an asynchronous manner. /// /// Value of a [GraphQL type][1] should be resolvable with `fut` binding representing a [`Future`] /// in the generated code, before including this piece of code. /// /// Optional `ty` argument may be used to annotate a concrete type of the resolving /// [GraphQL type][1] (the [`Future::Output`]). /// /// [`Future`]: std::future::Future /// [`Future::Output`]: std::future::Future::Output /// [1]: https://spec.graphql.org/October2021#sec-Types pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream { let ty = ty.map(|t| quote! { : #t }); quote! { ::std::boxed::Box::pin(::juniper::futures::FutureExt::then(fut, move |res #ty| async move { match ::juniper::IntoResolvable::into_resolvable(res, executor.context())? { ::core::option::Option::Some((ctx, r)) => { let subexec = executor.replaced_context(ctx); subexec.resolve_with_ctx_async(info, &r).await } ::core::option::Option::None => { ::core::result::Result::Ok(::juniper::Value::null()) } } })) } } juniper_codegen-0.16.0/src/common/mod.rs000064400000000000000000000030451046102023000162560ustar 00000000000000//! Common functions, definitions and extensions for code generation, used by this crate. pub(crate) mod default; pub(crate) mod deprecation; mod description; pub(crate) mod diagnostic; pub(crate) mod field; pub(crate) mod gen; pub(crate) mod parse; pub(crate) mod rename; pub(crate) mod scalar; mod span_container; use std::slice; pub(crate) use self::{description::Description, span_container::SpanContainer}; /// Checks whether the specified [`syn::Path`] equals to one of specified one-segment /// [`AttrNames::values`]. pub(crate) fn path_eq_single(path: &syn::Path, names: impl AttrNames) -> bool { path.segments.len() == 1 && names .values() .iter() .any(|name| path.segments[0].ident == name) } /// Filters the provided [`syn::Attribute`] to contain only ones with the /// specified `name`. pub(crate) fn filter_attrs<'a>( names: impl AttrNames + 'a, attrs: &'a [syn::Attribute], ) -> impl Iterator + 'a { attrs .iter() .filter(move |attr| path_eq_single(attr.path(), names)) } /// Input-type polymorphism helper for checking names of multiple attribute names. pub(crate) trait AttrNames: Copy { /// Returns values to be checked. fn values(&self) -> &[&str]; } impl AttrNames for &str { fn values(&self) -> &[&str] { slice::from_ref(self) } } impl AttrNames for &[&str] { fn values(&self) -> &[&str] { self } } impl AttrNames for [&str; N] { fn values(&self) -> &[&str] { self } } juniper_codegen-0.16.0/src/common/parse/attr.rs000064400000000000000000000060441046102023000175650ustar 00000000000000//! Common functions, definitions and extensions for parsing and modifying Rust attributes, used by //! this crate. use proc_macro2::{Span, TokenStream}; use syn::parse_quote; use crate::common::{path_eq_single, AttrNames}; /// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given /// `attr_path` and `attr_args`. /// /// This function is generally used for uniting `proc_macro_attribute` with its body attributes. pub(crate) fn unite( (attr_path, attr_args): (&str, &TokenStream), attrs: &[syn::Attribute], ) -> Vec { let mut full_attrs = Vec::with_capacity(attrs.len() + 1); let attr_path = syn::Ident::new(attr_path, Span::call_site()); full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] }); full_attrs.extend_from_slice(attrs); full_attrs } /// Strips all `attr_path` attributes from the given `attrs` collection. /// /// This function is generally used for removing duplicate attributes during `proc_macro_attribute` /// expansion, so avoid unnecessary expansion duplication. pub(crate) fn strip(names: impl AttrNames, attrs: Vec) -> Vec { attrs .into_iter() .filter(|attr| !path_eq_single(attr.path(), names)) .collect() } /// Common errors of parsing Rust attributes, appeared in this crate. pub(crate) mod err { use proc_macro2::Span; use syn::spanned::Spanned; /// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given /// `span`. #[must_use] pub(crate) fn dup_arg(span: S) -> syn::Error { syn::Error::new(span.as_span(), "duplicated attribute argument found") } /// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given `span`. #[must_use] pub(crate) fn unknown_arg(span: S, name: &str) -> syn::Error { syn::Error::new( span.as_span(), format!("unknown `{name}` attribute argument"), ) } /// Helper coercion for [`Span`] and [`Spanned`] types to use in function arguments. pub(crate) trait AsSpan { /// Returns the coerced [`Span`]. #[must_use] fn as_span(&self) -> Span; } impl AsSpan for Span { #[inline] fn as_span(&self) -> Self { *self } } impl AsSpan for &T { #[inline] fn as_span(&self) -> Span { self.span() } } } /// Handy extension of [`Option`] methods, used in this crate. pub(crate) trait OptionExt { type Inner; /// Transforms the `Option` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)` /// to `Err(err(v))`. fn none_or_else(self, err: F) -> Result<(), E> where F: FnOnce(Self::Inner) -> E; } impl OptionExt for Option { type Inner = T; fn none_or_else(self, err: F) -> Result<(), E> where F: FnOnce(T) -> E, { match self { Some(v) => Err(err(v)), None => Ok(()), } } } juniper_codegen-0.16.0/src/common/parse/downcaster.rs000064400000000000000000000060031046102023000207570ustar 00000000000000//! Common functions, definitions and extensions for parsing downcasting functions, used by GraphQL //! [interfaces][1] and [unions][2] definitions to downcast its type to a concrete implementer type. //! //! [1]: https://spec.graphql.org/October2021#sec-Interfaces //! [2]: https://spec.graphql.org/October2021#sec-Unions use proc_macro2::Span; use syn::{ext::IdentExt as _, spanned::Spanned as _}; use crate::common::parse::TypeExt as _; /// Parses downcasting output type from the downcaster method return type. /// /// # Errors /// /// If return type is invalid (not `Option<&OutputType>`), then returns the [`Span`] to display the /// corresponding error at. pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result { let ret_ty = match &ret_ty { syn::ReturnType::Type(_, ty) => &**ty, _ => return Err(ret_ty.span()), }; let path = match ret_ty.unparenthesized() { syn::Type::Path(syn::TypePath { qself: None, path }) => path, _ => return Err(ret_ty.span()), }; let (ident, args) = match path.segments.last() { Some(syn::PathSegment { ident, arguments: syn::PathArguments::AngleBracketed(generic), }) => (ident, &generic.args), _ => return Err(ret_ty.span()), }; if ident.unraw() != "Option" { return Err(ret_ty.span()); } if args.len() != 1 { return Err(ret_ty.span()); } let out_ty = match args.first() { Some(syn::GenericArgument::Type(inner_ty)) => match inner_ty.unparenthesized() { syn::Type::Reference(inner_ty) => { if inner_ty.mutability.is_some() { return Err(inner_ty.span()); } inner_ty.elem.unparenthesized().clone() } _ => return Err(ret_ty.span()), }, _ => return Err(ret_ty.span()), }; Ok(out_ty) } /// Parses context type used for downcasting from the downcaster method signature. /// /// Returns [`None`] if downcaster method doesn't accept context. /// /// # Errors /// /// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at. pub(crate) fn context_ty(sig: &syn::Signature) -> Result, Span> { match sig.receiver() { Some(rcv) => { if rcv.reference.is_none() || rcv.mutability.is_some() { return Err(rcv.span()); } } _ => return Err(sig.span()), } if sig.inputs.len() > 2 { return Err(sig.inputs.span()); } let second_arg_ty = match sig.inputs.iter().nth(1) { Some(syn::FnArg::Typed(arg)) => &*arg.ty, None => return Ok(None), _ => return Err(sig.inputs.span()), }; match second_arg_ty.unparenthesized() { syn::Type::Reference(ref_ty) => { if ref_ty.mutability.is_some() { return Err(ref_ty.span()); } Ok(Some(ref_ty.elem.unparenthesized().clone())) } ty => Err(ty.span()), } } juniper_codegen-0.16.0/src/common/parse/mod.rs000064400000000000000000000326441046102023000173770ustar 00000000000000//! Common functions, definitions and extensions for parsing, normalizing and modifying Rust syntax, //! used by this crate. pub(crate) mod attr; pub(crate) mod downcaster; use std::{any::TypeId, iter, mem}; use proc_macro2::Span; use quote::quote; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, parse_quote, punctuated::Punctuated, token::{self, Token}, visit_mut::VisitMut, }; /// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing. pub(crate) trait ParseBufferExt { /// Tries to parse `T` as the next token. /// /// Doesn't move [`ParseStream`]'s cursor if there is no `T`. fn try_parse(&self) -> syn::Result>; /// Checks whether next token is `T`. /// /// Doesn't move [`ParseStream`]'s cursor. #[must_use] fn is_next(&self) -> bool; /// Parses next token as [`syn::Ident`] _allowing_ Rust keywords, while default [`Parse`] /// implementation for [`syn::Ident`] disallows keywords. /// /// Always moves [`ParseStream`]'s cursor. fn parse_any_ident(&self) -> syn::Result; /// Checks whether next token is a wrapper `W` and if yes, then parses the wrapped tokens as `T` /// [`Punctuated`] with `P`. Otherwise, parses just `T`. /// /// Always moves [`ParseStream`]'s cursor. fn parse_maybe_wrapped_and_punctuated(&self) -> syn::Result> where T: Parse, W: Default + Token + 'static, P: Default + Parse + Token; } impl<'a> ParseBufferExt for ParseBuffer<'a> { fn try_parse(&self) -> syn::Result> { Ok(if self.is_next::() { Some(self.parse()?) } else { None }) } fn is_next(&self) -> bool { self.lookahead1().peek(|_| T::default()) } fn parse_any_ident(&self) -> syn::Result { self.call(syn::Ident::parse_any) } fn parse_maybe_wrapped_and_punctuated(&self) -> syn::Result> where T: Parse, W: Default + Token + 'static, P: Default + Parse + Token, { Ok(if self.is_next::() { let inner; if TypeId::of::() == TypeId::of::() { let _ = syn::bracketed!(inner in self); } else if TypeId::of::() == TypeId::of::() { let _ = syn::braced!(inner in self); } else if TypeId::of::() == TypeId::of::() { let _ = syn::parenthesized!(inner in self); } else { unimplemented!( "ParseBufferExt::parse_maybe_wrapped_and_punctuated supports only brackets, \ braces and parentheses as wrappers.", ); } Punctuated::parse_terminated(&inner)? } else { Punctuated::from_iter(iter::once(self.parse::()?)) }) } } /// Extension of [`syn::Type`] providing common function widely used by this crate for parsing. pub(crate) trait TypeExt { /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given /// one (unwraps nested [`syn::TypeParen`]s asap). #[must_use] fn unparenthesized(&self) -> &Self; /// Retrieves the inner [`syn::Type`] from the given reference type, or just /// returns "as is" if the type is not a reference. /// /// Also, makes the type [`TypeExt::unparenthesized`], if possible. #[must_use] fn unreferenced(&self) -> &Self; /// Iterates mutably over all the lifetime parameters of this [`syn::Type`] /// with the given `func`tion. fn lifetimes_iter_mut(&mut self, func: &mut F); /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except /// the `'static` ones), making it suitable for using in contexts with /// inferring. fn lifetimes_anonymized(&mut self); /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any. #[must_use] fn topmost_ident(&self) -> Option<&syn::Ident>; } impl TypeExt for syn::Type { fn unparenthesized(&self) -> &Self { match self { Self::Paren(ty) => ty.elem.unparenthesized(), Self::Group(ty) => ty.elem.unparenthesized(), ty => ty, } } fn unreferenced(&self) -> &Self { match self.unparenthesized() { Self::Reference(ref_ty) => &ref_ty.elem, ty => ty, } } fn lifetimes_iter_mut(&mut self, func: &mut F) { use syn::{GenericArgument as GA, Type as T}; fn iter_path(path: &mut syn::Path, func: &mut F) { for seg in path.segments.iter_mut() { match &mut seg.arguments { syn::PathArguments::AngleBracketed(angle) => { for arg in angle.args.iter_mut() { match arg { GA::Lifetime(lt) => func(lt), GA::Type(ty) => ty.lifetimes_iter_mut(func), GA::AssocType(a) => a.ty.lifetimes_iter_mut(func), GA::Constraint(_) | GA::AssocConst(_) | GA::Const(_) => {} // Following the `syn` idiom for exhaustive matching on `Type`: // https://docs.rs/syn/2.0.38/src/syn/ty.rs.html#64-79 // TODO: #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] // https://github.com/rust-lang/rust/issues/89554 _ => unimplemented!(), } } } syn::PathArguments::Parenthesized(args) => { for ty in args.inputs.iter_mut() { ty.lifetimes_iter_mut(func) } if let syn::ReturnType::Type(_, ty) = &mut args.output { (*ty).lifetimes_iter_mut(func) } } syn::PathArguments::None => {} } } } match self { T::Array(syn::TypeArray { elem, .. }) | T::Group(syn::TypeGroup { elem, .. }) | T::Paren(syn::TypeParen { elem, .. }) | T::Ptr(syn::TypePtr { elem, .. }) | T::Slice(syn::TypeSlice { elem, .. }) => (*elem).lifetimes_iter_mut(func), T::Tuple(syn::TypeTuple { elems, .. }) => { for ty in elems.iter_mut() { ty.lifetimes_iter_mut(func) } } T::ImplTrait(syn::TypeImplTrait { bounds, .. }) | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { for bound in bounds.iter_mut() { match bound { syn::TypeParamBound::Lifetime(lt) => func(lt), syn::TypeParamBound::Trait(bound) => { if bound.lifetimes.is_some() { todo!("Iterating over HRTB lifetimes in trait is not yet supported") } iter_path(&mut bound.path, func) } syn::TypeParamBound::Verbatim(_) => {} // Following the `syn` idiom for exhaustive matching on `Type`: // https://docs.rs/syn/2.0.38/src/syn/ty.rs.html#64-79 // TODO: #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] // https://github.com/rust-lang/rust/issues/89554 _ => unimplemented!(), } } } T::Reference(ref_ty) => { if let Some(lt) = ref_ty.lifetime.as_mut() { func(lt) } (*ref_ty.elem).lifetimes_iter_mut(func) } T::Path(ty) => iter_path(&mut ty.path, func), // These types unlikely will be used as GraphQL types. T::BareFn(_) | T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) => {} // Following the `syn` idiom for exhaustive matching on `Type`: // https://docs.rs/syn/2.0.38/src/syn/ty.rs.html#64-79 // TODO: #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] // https://github.com/rust-lang/rust/issues/89554 _ => unimplemented!(), } } fn lifetimes_anonymized(&mut self) { self.lifetimes_iter_mut(&mut |lt| { if lt.ident != "_" && lt.ident != "static" { lt.ident = syn::Ident::new("_", Span::call_site()); } }); } fn topmost_ident(&self) -> Option<&syn::Ident> { match self.unparenthesized() { syn::Type::Path(p) => Some(&p.path), syn::Type::Reference(r) => match (*r.elem).unparenthesized() { syn::Type::Path(p) => Some(&p.path), syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() { syn::TypeParamBound::Trait(b) => Some(&b.path), _ => None, }, _ => None, }, _ => None, }? .segments .last() .map(|s| &s.ident) } } /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing. pub(crate) trait GenericsExt { /// Removes all default types out of type parameters and const parameters in these /// [`syn::Generics`]. fn remove_defaults(&mut self); /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); /// Replaces generic parameters in the given [`syn::Type`] with default /// ones, provided by these [`syn::Generics`]. fn replace_type_with_defaults(&self, ty: &mut syn::Type); /// Replaces generic parameters in the given [`syn::TypePath`] with default /// ones, provided by these [`syn::Generics`]. fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath); } impl GenericsExt for syn::Generics { fn remove_defaults(&mut self) { use syn::GenericParam as P; for p in &mut self.params { match p { P::Type(p) => { p.eq_token = None; p.default = None; } P::Lifetime(_) => {} P::Const(p) => { p.eq_token = None; p.default = None; } } } } fn move_bounds_to_where_clause(&mut self) { use syn::GenericParam as P; let _ = self.make_where_clause(); let where_clause = self.where_clause.as_mut().unwrap(); for p in &mut self.params { match p { P::Type(p) => { if p.colon_token.is_some() { p.colon_token = None; let bounds = mem::take(&mut p.bounds); let ty = &p.ident; where_clause.predicates.push(parse_quote! { #ty: #bounds }); } } P::Lifetime(p) => { if p.colon_token.is_some() { p.colon_token = None; let bounds = mem::take(&mut p.bounds); let lt = &p.lifetime; where_clause.predicates.push(parse_quote! { #lt: #bounds }); } } P::Const(_) => {} } } } fn replace_type_with_defaults(&self, ty: &mut syn::Type) { ReplaceWithDefaults(self).visit_type_mut(ty) } fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath) { ReplaceWithDefaults(self).visit_type_path_mut(ty) } } /// Replaces [`Generics`] with default values: /// - `'static` for [`Lifetime`]s; /// - `::juniper::DefaultScalarValue` for [`Type`]s. /// /// [`Generics`]: syn::Generics /// [`Lifetime`]: syn::Lifetime /// [`Type`]: syn::Type struct ReplaceWithDefaults<'a>(&'a syn::Generics); impl<'a> VisitMut for ReplaceWithDefaults<'a> { fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { match arg { syn::GenericArgument::Lifetime(lf) => { *lf = parse_quote! { 'static }; } syn::GenericArgument::Type(ty) => { let is_generic = self .0 .params .iter() .filter_map(|par| match par { syn::GenericParam::Type(ty) => Some(&ty.ident), _ => None, }) .any(|par| { let par = quote! { #par }.to_string(); let ty = quote! { #ty }.to_string(); par == ty }); if is_generic { // Replace with `DefaultScalarValue` instead of `()` // because generic parameter may be scalar. *ty = parse_quote!(::juniper::DefaultScalarValue); } } _ => {} } } } juniper_codegen-0.16.0/src/common/rename.rs000064400000000000000000000102641046102023000167470ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! of `#[graphql(rename_all = ...)]` attribute. use std::str::FromStr; use syn::parse::{Parse, ParseStream}; /// Possible ways to rename all [GraphQL fields][1] or [GrqphQL enum values][2]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields /// [2]: https://spec.graphql.org/October2021#sec-Enum-Value #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Policy { /// Do nothing, and use the default conventions renaming. None, /// Rename in `camelCase` style. CamelCase, /// Rename in `SCREAMING_SNAKE_CASE` style. ScreamingSnakeCase, } impl Policy { /// Applies this [`Policy`] to the given `name`. pub(crate) fn apply(&self, name: &str) -> String { match self { Self::None => name.into(), Self::CamelCase => to_camel_case(name), Self::ScreamingSnakeCase => to_upper_snake_case(name), } } } impl FromStr for Policy { type Err = (); fn from_str(rule: &str) -> Result { match rule { "none" => Ok(Self::None), "camelCase" => Ok(Self::CamelCase), "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase), _ => Err(()), } } } impl TryFrom for Policy { type Error = syn::Error; fn try_from(lit: syn::LitStr) -> syn::Result { Self::from_str(&lit.value()) .map_err(|_| syn::Error::new(lit.span(), "unknown renaming policy")) } } impl Parse for Policy { fn parse(input: ParseStream<'_>) -> syn::Result { Self::try_from(input.parse::()?) } } // NOTE: duplicated from juniper crate! fn to_camel_case(s: &str) -> String { let mut dest = String::new(); // Handle `_` and `__` to be more friendly with the `_var` convention for // unused variables, and GraphQL introspection identifiers. let s_iter = if let Some(s) = s.strip_prefix("__") { dest.push_str("__"); s } else { s.strip_prefix('_').unwrap_or(s) } .split('_') .enumerate(); for (i, part) in s_iter { if i > 0 && part.len() == 1 { dest.push_str(&part.to_uppercase()); } else if i > 0 && part.len() > 1 { let first = part .chars() .next() .unwrap() .to_uppercase() .collect::(); let second = &part[1..]; dest.push_str(&first); dest.push_str(second); } else if i == 0 { dest.push_str(part); } } dest } fn to_upper_snake_case(s: &str) -> String { let mut last_lower = false; let mut upper = String::new(); for c in s.chars() { if c == '_' { last_lower = false; } else if c.is_lowercase() { last_lower = true; } else if c.is_uppercase() { if last_lower { upper.push('_'); } last_lower = false; } for u in c.to_uppercase() { upper.push(u); } } upper } #[cfg(test)] mod to_camel_case_tests { use super::to_camel_case; #[test] fn converts_correctly() { for (input, expected) in [ ("test", "test"), ("_test", "test"), ("__test", "__test"), ("first_second", "firstSecond"), ("first_", "first"), ("a_b_c", "aBC"), ("a_bc", "aBc"), ("a_b", "aB"), ("a", "a"), ("", ""), ] { assert_eq!(to_camel_case(input), expected); } } } #[cfg(test)] mod to_upper_snake_case_tests { use super::to_upper_snake_case; #[test] fn converts_correctly() { for (input, expected) in [ ("abc", "ABC"), ("a_bc", "A_BC"), ("ABC", "ABC"), ("A_BC", "A_BC"), ("SomeInput", "SOME_INPUT"), ("someInput", "SOME_INPUT"), ("someINpuT", "SOME_INPU_T"), ("some_INpuT", "SOME_INPU_T"), ] { assert_eq!(to_upper_snake_case(input), expected); } } } juniper_codegen-0.16.0/src/common/scalar.rs000064400000000000000000000120351046102023000167430ustar 00000000000000//! Common functions, definitions and extensions for parsing and code generation //! related to [`ScalarValue`]. //! //! [`ScalarValue`]: juniper::ScalarValue use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, }; /// Possible values of `#[graphql(scalar = ...)]` attribute. #[derive(Clone, Debug)] pub(crate) enum AttrValue { /// Concrete Rust type (like `DefaultScalarValue`). /// /// [`ScalarValue`]: juniper::ScalarValue Concrete(syn::Type), /// Generic Rust type parameter with a bound predicate /// (like `S: ScalarValue + Send + Sync`). /// /// [`ScalarValue`]: juniper::ScalarValue Generic(syn::PredicateType), } impl Parse for AttrValue { fn parse(input: ParseStream<'_>) -> syn::Result { if input.fork().parse::().is_ok() { let pred = input.parse().unwrap(); if let syn::WherePredicate::Type(p) = pred { Ok(Self::Generic(p)) } else { Err(syn::Error::new( pred.span(), "only type predicates are allowed here", )) } } else { input.parse::().map(Self::Concrete) } } } impl ToTokens for AttrValue { fn to_tokens(&self, into: &mut TokenStream) { match self { Self::Concrete(ty) => ty.to_tokens(into), Self::Generic(pred) => pred.to_tokens(into), } } } /// [`ScalarValue`] parametrization of the code generation. /// /// [`ScalarValue`]: juniper::ScalarValue #[derive(Clone, Debug)] pub(crate) enum Type { /// Concrete Rust type is specified as [`ScalarValue`]. /// /// [`ScalarValue`]: juniper::ScalarValue Concrete(syn::Type), /// One of type parameters of the original type is specified as [`ScalarValue`]. /// /// The original type is the type that the code is generated for. /// /// [`ScalarValue`]: juniper::ScalarValue ExplicitGeneric(syn::Ident), /// [`ScalarValue`] parametrization is assumed to be generic and is not specified /// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`). /// /// [`ScalarValue`]: juniper::ScalarValue ImplicitGeneric(Option), } impl ToTokens for Type { fn to_tokens(&self, into: &mut TokenStream) { self.ty().to_tokens(into) } } impl Type { /// Indicates whether this [`Type`] is generic. #[must_use] pub(crate) fn is_generic(&self) -> bool { matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_)) } /// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`]. #[must_use] pub(crate) fn is_implicit_generic(&self) -> bool { matches!(self, Self::ImplicitGeneric(_)) } /// Returns additional trait bounds behind this [`Type`], if any. #[must_use] pub(crate) fn bounds(&self) -> Option { if let Self::ImplicitGeneric(Some(pred)) = self { Some(syn::WherePredicate::Type(pred.clone())) } else { None } } /// Returns a type identifier which represents this [`Type`]. #[must_use] pub(crate) fn ty(&self) -> syn::Type { match self { Self::Concrete(ty) => ty.clone(), Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(), Self::ImplicitGeneric(None) => parse_quote! { __S }, } } /// Returns a default [`ScalarValue`] type that is compatible with this [`Type`]. /// /// [`ScalarValue`]: juniper::ScalarValue #[must_use] pub(crate) fn default_ty(&self) -> syn::Type { match self { Self::Concrete(ty) => ty.clone(), Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_) => { parse_quote! { ::juniper::DefaultScalarValue } } } } /// Parses [`Type`] from the given `explicit` [`AttrValue`] (if any), /// checking whether it's contained in the giving `generics`. #[must_use] pub(crate) fn parse(explicit: Option<&AttrValue>, generics: &syn::Generics) -> Self { match explicit { Some(AttrValue::Concrete(scalar_ty)) => generics .params .iter() .find_map(|p| { if let syn::GenericParam::Type(tp) = p { let ident = &tp.ident; let ty: syn::Type = parse_quote! { #ident }; if &ty == scalar_ty { return Some(&tp.ident); } } None }) .map(|ident| Self::ExplicitGeneric(ident.clone())) .unwrap_or_else(|| Self::Concrete(scalar_ty.clone())), Some(AttrValue::Generic(pred)) => Self::ImplicitGeneric(Some(pred.clone())), None => Self::ImplicitGeneric(None), } } } juniper_codegen-0.16.0/src/common/span_container.rs000064400000000000000000000033401046102023000205000ustar 00000000000000use std::{ hash::{Hash, Hasher}, ops, }; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; #[derive(Clone, Copy, Debug)] pub(crate) struct SpanContainer { expr: Option, ident: Span, val: T, } impl ToTokens for SpanContainer { fn to_tokens(&self, tokens: &mut TokenStream) { self.val.to_tokens(tokens) } } impl SpanContainer { pub(crate) fn new(ident: Span, expr: Option, val: T) -> Self { Self { expr, ident, val } } pub(crate) fn span_ident(&self) -> Span { self.ident } pub(crate) fn span_joined(&self) -> Span { if let Some(s) = self.expr { // TODO: Use `Span::join` once stabilized and available on stable: // https://github.com/rust-lang/rust/issues/54725 // self.ident.join(s).unwrap() // At the moment, just return the second, more meaningful part. s } else { self.ident } } pub(crate) fn into_inner(self) -> T { self.val } } impl AsRef for SpanContainer { fn as_ref(&self) -> &T { &self.val } } impl ops::Deref for SpanContainer { type Target = T; fn deref(&self) -> &Self::Target { &self.val } } impl PartialEq for SpanContainer { fn eq(&self, other: &Self) -> bool { self.val == other.val } } impl Eq for SpanContainer {} impl PartialEq for SpanContainer { fn eq(&self, other: &T) -> bool { &self.val == other } } impl Hash for SpanContainer { fn hash(&self, state: &mut H) where H: Hasher, { self.val.hash(state) } } juniper_codegen-0.16.0/src/graphql_enum/derive.rs000064400000000000000000000076321046102023000201550ustar 00000000000000//! Code generation for `#[derive(GraphQLEnum)]` macro. use std::collections::HashSet; use proc_macro2::TokenStream; use quote::ToTokens as _; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::common::{diagnostic, rename, scalar, SpanContainer}; use super::{ContainerAttr, Definition, ValueDefinition, VariantAttr}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLEnum)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::EnumDerive; /// Expands `#[derive(GraphQLEnum)]` macro into generated code. pub(crate) fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?; let data = if let syn::Data::Enum(data) = &ast.data { data } else { return Err(ERR.custom_error(ast.span(), "can only be derived on enums")); }; let mut has_ignored_variants = false; let renaming = attr .rename_values .map(SpanContainer::into_inner) .unwrap_or(rename::Policy::ScreamingSnakeCase); let values = data .variants .iter() .filter_map(|v| { parse_value(v, renaming).or_else(|| { has_ignored_variants = true; None }) }) .collect::>(); diagnostic::abort_if_dirty(); if values.is_empty() { return Err(ERR.custom_error( data.variants.span(), "expected at least 1 non-ignored enum variant", )); } let unique_values = values.iter().map(|v| &v.name).collect::>(); if unique_values.len() != values.len() { return Err(ERR.custom_error( data.variants.span(), "expected all GraphQL enum values to have unique names", )); } let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.unraw().to_string()) .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| ast.ident.span()), ); } let context = attr .context .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner); let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let definition = Definition { ident: ast.ident, generics: ast.generics, name, description: attr.description.map(SpanContainer::into_inner), context, scalar, values, has_ignored_variants, }; Ok(definition.into_token_stream()) } /// Parses a [`ValueDefinition`] from the given Rust enum variant definition. /// /// Returns [`None`] if the parsing fails, or the enum variant is ignored. fn parse_value(v: &syn::Variant, renaming: rename::Policy) -> Option { let attr = VariantAttr::from_attrs("graphql", &v.attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } if !v.fields.is_empty() { err_variant_with_fields(&v.fields)?; } let name = attr .name .map_or_else( || renaming.apply(&v.ident.unraw().to_string()), SpanContainer::into_inner, ) .into_boxed_str(); Some(ValueDefinition { ident: v.ident.clone(), name, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), }) } /// Emits "no fields allowed for non-ignored variants" [`syn::Error`] pointing /// to the given `span`. pub fn err_variant_with_fields(span: &S) -> Option { ERR.emit_custom(span.span(), "no fields allowed for non-ignored variants"); None } juniper_codegen-0.16.0/src/graphql_enum/mod.rs000064400000000000000000000667171046102023000174670ustar 00000000000000//! Code generation for [GraphQL enums][0]. //! //! [0]: https://spec.graphql.org/October2021#sec-Enums pub(crate) mod derive; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, }; use crate::common::{ deprecation, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, rename, scalar, Description, SpanContainer, }; /// Available arguments behind `#[graphql]` attribute placed on a Rust enum /// definition, when generating code for a [GraphQL enum][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums #[derive(Debug, Default)] struct ContainerAttr { /// Explicitly specified name of this [GraphQL enum][0]. /// /// If [`None`], then Rust enum name will be used by default. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums name: Option>, /// Explicitly specified [description][2] of this [GraphQL enum][0]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL enum][0] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [0]: https://spec.graphql.org/October2021#sec-Enums context: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to resolve this [GraphQL enum][0] type with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Enums scalar: Option>, /// Explicitly specified [`rename::Policy`] for all [values][1] of this /// [GraphQL enum][0]. /// /// If [`None`], then the [`rename::Policy::ScreamingSnakeCase`] will be /// applied by default. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition rename_values: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. is_internal: bool, } impl Parse for ContainerAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; out.context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "rename_all" => { input.parse::()?; let val = input.parse::()?; out.rename_values .replace(SpanContainer::new( ident.span(), Some(val.span()), val.try_into()?, )) .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl ContainerAttr { /// Tries to merge two [`ContainerAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), rename_values: try_merge_opt!(rename_values: self, another), is_internal: self.is_internal || another.is_internal, }) } /// Parses [`ContainerAttr`] from the given multiple `name`d /// [`syn::Attribute`]s placed on a trait definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// Available arguments behind `#[graphql]` attribute when generating code for /// a [GraphQL enum][0]'s [value][1]. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value #[derive(Debug, Default)] struct VariantAttr { /// Explicitly specified name of this [GraphQL enum value][1]. /// /// If [`None`], then Rust enum variant's name will be used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value name: Option>, /// Explicitly specified [description][2] of this [GraphQL enum value][1]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified [deprecation][2] of this [GraphQL enum value][1]. /// /// If [`None`], then Rust `#[deprecated]` attribute will be used as the /// [deprecation][2], if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value /// [2]: https://spec.graphql.org/October2021#sec--deprecated /// [3]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L deprecated: Option>, /// Explicitly specified marker for the Rust enum variant to be ignored and /// not included into the code generated for a [GraphQL enum][0] /// implementation. /// /// [0]: https://spec.graphql.org/October20210#sec-Enums ignore: Option>, } impl Parse for VariantAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "deprecated" => { let directive = input.parse::()?; out.deprecated .replace(SpanContainer::new( ident.span(), directive.reason.as_ref().map(|r| r.span()), directive, )) .none_or_else(|_| err::dup_arg(&ident))? } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl VariantAttr { /// Tries to merge two [`VariantAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), }) } /// Parses [`VariantAttr`] from the given multiple `name`d /// [`syn::Attribute`]s placed on a trait definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } if attr.deprecated.is_none() { attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; } Ok(attr) } } /// Representation of a [GraphQL enum value][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value #[derive(Debug)] struct ValueDefinition { /// [`Ident`] of the Rust enum variant behind this [GraphQL enum value][1]. /// /// [`Ident`]: syn::Ident /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value ident: syn::Ident, /// Name of this [GraphQL enum value][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value name: Box, /// [Description][2] of this [GraphQL enum value][1] to put into GraphQL /// schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option, /// [Deprecation][2] of this [GraphQL enum value][1] to put into GraphQL /// schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value /// [2]: https://spec.graphql.org/October2021#sec--deprecated deprecated: Option, } /// Representation of a [GraphQL enum][0] for code generation. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums struct Definition { /// [`Ident`] of the Rust enum behind this [GraphQL enum][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums ident: syn::Ident, /// [`Generics`] of the Rust enum behind this [GraphQL enum][0]. /// /// [`Generics`]: syn::Generics /// [0]: https://spec.graphql.org/October2021#sec-Enums generics: syn::Generics, /// Name of this [GraphQL enum][0] in GraphQL schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums name: Box, /// [Description][2] of this [GraphQL enum][0] to put into GraphQL schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL enum][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [0]: https://spec.graphql.org/October2021#sec-Enums context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL enum][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Enums scalar: scalar::Type, /// [Values][1] of this [GraphQL enum][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition values: Vec, /// Indicates whether the Rust enum behind this [GraphQL enum][0] contains /// ignored variants. /// /// [0]: https://spec.graphql.org/October2021#sec-Enums has_ignored_variants: bool, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_input_and_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL enum][0]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_input_and_output_type_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::IsInputType<#scalar> for #ident #ty_generics #where_clause {} #[automatically_derived] impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ident #ty_generics #where_clause {} } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL enum][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_graphql_type_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let name = &self.name; let description = &self.description; let variants_meta = self.values.iter().map(|v| { let v_name = &v.name; let v_description = &v.description; let v_deprecation = &v.deprecated; quote! { ::juniper::meta::EnumValue::new(#v_name) #v_description #v_deprecation } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLType<#scalar> for #ident #ty_generics #where_clause { fn name( _ : &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { let variants = [#( #variants_meta ),*]; registry.build_enum_type::<#ident #ty_generics>(info, &variants) #description .into_meta() } } } } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL enum][0]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_graphql_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let context = &self.context; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let variants = self.values.iter().map(|v| { let ident = &v.ident; let name = &v.name; quote! { Self::#ident => ::core::result::Result::Ok(::juniper::Value::scalar( ::std::string::String::from(#name), )), } }); let ignored = self.has_ignored_variants.then(|| { quote! { _ => ::core::result::Result::Err(::juniper::FieldError::<#scalar>::from( "Cannot resolve ignored enum variant", )), } }); quote! { impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ident #ty_generics #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } fn resolve( &self, _: &(), _: ::core::option::Option<&[::juniper::Selection<#scalar>]>, _: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { match self { #( #variants )* #ignored } } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL enum][0]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_graphql_value_async_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident #ty_generics #where_clause { fn resolve_async<'__a>( &'__a self, info: &'__a Self::TypeInfo, selection_set: ::core::option::Option<&'__a [::juniper::Selection<#scalar>]>, executor: &'__a ::juniper::Executor, ) -> ::juniper::BoxFuture<'__a, ::juniper::ExecutionResult<#scalar>> { let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); ::std::boxed::Box::pin(::juniper::futures::future::ready(v)) } } } } /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL enum][0]. /// /// [`FromInputValue`]: juniper::FromInputValue /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_from_input_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let variants = self.values.iter().map(|v| { let ident = &v.ident; let name = &v.name; quote! { ::core::option::Option::Some(#name) => ::core::result::Result::Ok(Self::#ident), } }); quote! { impl #impl_generics ::juniper::FromInputValue<#scalar> for #ident #ty_generics #where_clause { type Error = ::std::string::String; fn from_input_value( v: &::juniper::InputValue<#scalar>, ) -> ::core::result::Result { match v.as_enum_value().or_else(|| v.as_string_value()) { #( #variants )* _ => ::core::result::Result::Err( ::std::format!("Unknown enum value: {}", v), ), } } } } } /// Returns generated code implementing [`ToInputValue`] trait for this /// [GraphQL enum][0]. /// /// [`ToInputValue`]: juniper::ToInputValue /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_to_input_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let variants = self.values.iter().map(|v| { let var_ident = &v.ident; let name = &v.name; quote! { #ident::#var_ident => ::juniper::InputValue::<#scalar>::scalar( ::std::string::String::from(#name), ), } }); let ignored = self.has_ignored_variants.then(|| { quote! { _ => ::core::panic!("Cannot resolve ignored enum variant"), } }); quote! { impl #impl_generics ::juniper::ToInputValue<#scalar> for #ident #ty_generics #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { match self { #( #variants )* #ignored } } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL enum][0]. /// /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [0]: https://spec.graphql.org/October2021#sec-Enums fn impl_reflection_traits_tokens(&self) -> TokenStream { let ident = &self.ident; let name = &self.name; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident #ty_generics #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[>::NAME]; } impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident #ty_generics #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this enum. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ident = &self.ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ident #ty_generics } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } generics } } juniper_codegen-0.16.0/src/graphql_input_object/derive.rs000064400000000000000000000075001046102023000216700ustar 00000000000000//! Code generation for `#[derive(GraphQLInputObject)]` macro. use std::collections::HashSet; use proc_macro2::TokenStream; use quote::ToTokens as _; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::common::{diagnostic, rename, scalar, SpanContainer}; use super::{ContainerAttr, Definition, FieldAttr, FieldDefinition}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLInputObject)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::InputObjectDerive; /// Expands `#[derive(GraphQLInputObject)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?; let data = if let syn::Data::Struct(data) = &ast.data { data } else { return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); }; let renaming = attr .rename_fields .map(SpanContainer::into_inner) .unwrap_or(rename::Policy::CamelCase); let is_internal = attr.is_internal; let fields = data .fields .iter() .filter_map(|f| parse_field(f, renaming, is_internal)) .collect::>(); diagnostic::abort_if_dirty(); if !fields.iter().any(|f| !f.ignored) { return Err(ERR.custom_error(data.fields.span(), "expected at least 1 non-ignored field")); } let unique_fields = fields.iter().map(|v| &v.name).collect::>(); if unique_fields.len() != fields.len() { return Err(ERR.custom_error( data.fields.span(), "expected all fields to have unique names", )); } let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.unraw().to_string()) .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| ast.ident.span()), ); } let context = attr .context .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner); let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let definition = Definition { ident: ast.ident, generics: ast.generics, name, description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, }; Ok(definition.into_token_stream()) } /// Parses a [`FieldDefinition`] from the given struct field definition. /// /// Returns [`None`] if the parsing fails. fn parse_field( f: &syn::Field, renaming: rename::Policy, is_internal: bool, ) -> Option { let field_attr = FieldAttr::from_attrs("graphql", &f.attrs) .map_err(diagnostic::emit_error) .ok()?; let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?; let name = field_attr .name .map_or_else( || renaming.apply(&ident.unraw().to_string()), SpanContainer::into_inner, ) .into_boxed_str(); if !is_internal && name.starts_with("__") { ERR.no_double_underscore(f.span()); } Some(FieldDefinition { ident: ident.clone(), ty: f.ty.clone(), default: field_attr.default.map(SpanContainer::into_inner), name, description: field_attr.description.map(SpanContainer::into_inner), ignored: field_attr.ignore.is_some(), }) } /// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. pub(crate) fn err_unnamed_field(span: &S) -> Option { ERR.emit_custom(span.span(), "expected named struct field"); None } juniper_codegen-0.16.0/src/graphql_input_object/mod.rs000064400000000000000000000706511046102023000212000ustar 00000000000000//! Code generation for [GraphQL input objects][0]. //! //! [0]: https://spec.graphql.org/October2021#sec-Input-Objects pub(crate) mod derive; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, token, }; use crate::common::{ default, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, rename, scalar, Description, SpanContainer, }; /// Available arguments behind `#[graphql]` attribute placed on a Rust struct /// definition, when generating code for a [GraphQL input object][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[derive(Debug, Default)] struct ContainerAttr { /// Explicitly specified name of this [GraphQL input object][0]. /// /// If [`None`], then Rust struct name will be used by default. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects name: Option>, /// Explicitly specified [description][2] of this [GraphQL input object][0]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL input object][0] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects context: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to use for resolving this [GraphQL input object][0] type /// with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: Option>, /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL input object][0]. /// /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by /// default. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. is_internal: bool, } impl Parse for ContainerAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; out.context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "rename_all" => { input.parse::()?; let val = input.parse::()?; out.rename_fields .replace(SpanContainer::new( ident.span(), Some(val.span()), val.try_into()?, )) .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl ContainerAttr { /// Tries to merge two [`ContainerAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) } /// Parses [`ContainerAttr`] from the given multiple `name`d /// [`syn::Attribute`]s placed on a struct or impl block definition. pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// Available arguments behind `#[graphql]` attribute when generating code for /// [GraphQL input object][0]'s [field][1]. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition #[derive(Debug, Default)] struct FieldAttr { /// Explicitly specified name of this [GraphQL input object field][1]. /// /// If [`None`], then Rust struct field name will be used by default. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition name: Option>, /// Explicitly specified [default value][2] of this /// [GraphQL input object field][1] to be used used in case a field value is /// not provided. /// /// If [`None`], the this [field][1] will have no [default value][2]. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option>, /// Explicitly specified [description][2] of this /// [GraphQL input object field][1]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified marker for the Rust struct field to be ignored and /// not included into the code generated for a [GraphQL input object][0] /// implementation. /// /// Ignored Rust struct fields still consider the [`default`] attribute's /// argument. /// /// [`default`]: Self::default /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects ignore: Option>, } impl Parse for FieldAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "default" => { let val = input.parse::()?; out.default .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl FieldAttr { /// Tries to merge two [`FieldAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), default: try_merge_opt!(default: self, another), description: try_merge_opt!(description: self, another), ignore: try_merge_opt!(ignore: self, another), }) } /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a trait definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// Representation of a [GraphQL input object field][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition #[derive(Debug)] struct FieldDefinition { /// [`Ident`] of the Rust struct field behind this /// [GraphQL input object field][1]. /// /// [`Ident`]: syn::Ident /// [1]: https://spec.graphql.org/October2021#InputValueDefinition ident: syn::Ident, /// Rust type that this [GraphQL input object field][1] is represented with. /// /// It should contain all its generics, if any. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition ty: syn::Type, /// [Default value][2] of this [GraphQL input object field][1] to be used in /// case a [field][1] value is not provided. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option, /// Name of this [GraphQL input object field][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition name: Box, /// [Description][2] of this [GraphQL input object field][1] to put into /// GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option, /// Indicator whether the Rust struct field behinds this /// [GraphQL input object field][1] is being ignored and should not be /// included into the generated code. /// /// Ignored Rust struct fields still consider the [`default`] attribute's /// argument. /// /// [`default`]: Self::default /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects ignored: bool, } /// Representation of [GraphQL input object][0] for code generation. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[derive(Debug)] struct Definition { /// [`Ident`] of the Rust struct behind this [GraphQL input object][0]. /// /// [`Ident`]: syn::Ident /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects ident: syn::Ident, /// [`Generics`] of the Rust enum behind this [GraphQL input object][0]. /// /// [`Generics`]: syn::Generics /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects generics: syn::Generics, /// Name of this [GraphQL input object][0] in GraphQL schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects name: Box, /// [Description][2] of this [GraphQL input object][0] to put into GraphQL /// schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL input object][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL input object][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: scalar::Type, /// [Fields][1] of this [GraphQL input object][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition fields: Vec, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_input_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing [`marker::IsInputType`] trait for /// this [GraphQL input object][0]. /// /// [`marker::IsInputType`]: juniper::marker::IsInputType /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_input_type_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let assert_fields_input_values = self.fields.iter().filter_map(|f| { let ty = &f.ty; (!f.ignored).then(|| { quote! { <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } }) }); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::IsInputType<#scalar> for #ident #ty_generics #where_clause { fn mark() { #( #assert_fields_input_values )* } } } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL input object][0]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let name = &self.name; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let description = &self.description; let fields = self.fields.iter().filter_map(|f| { let ty = &f.ty; let name = &f.name; (!f.ignored).then(|| { let arg = if let Some(default) = &f.default { quote! { .arg_with_default::<#ty>(#name, &#default, info) } } else { quote! { .arg::<#ty>(#name, info) } }; let description = &f.description; quote! { registry #arg #description } }) }); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLType<#scalar> for #ident #ty_generics #where_clause { fn name( _: &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar>, ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { let fields = [#( #fields ),*]; registry .build_input_object_type::<#ident #ty_generics>(info, &fields) #description .into_meta() } } } } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL input object][0]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let context = &self.context; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ident #ty_generics #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL input object][0]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[allow(non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident #ty_generics #where_clause {} } } /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL input object][0]. /// /// [`FromInputValue`]: juniper::FromInputValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_from_input_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let fields = self.fields.iter().map(|f| { let ident = &f.ident; let construct = if f.ignored { f.default.as_ref().map_or_else( || { let expr = default::Value::default(); quote! { #expr } }, |expr| quote! { #expr }, ) } else { let name = &f.name; let fallback = f.default.as_ref().map_or_else( || { quote! { ::juniper::FromInputValue::<#scalar>::from_implicit_null() .map_err(::juniper::IntoFieldError::into_field_error)? } }, |expr| quote! { #expr }, ); quote! { match obj.get(#name) { ::core::option::Option::Some(v) => { ::juniper::FromInputValue::<#scalar>::from_input_value(v) .map_err(::juniper::IntoFieldError::into_field_error)? } ::core::option::Option::None => { #fallback } } } }; quote! { #ident: { #construct }, } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::FromInputValue<#scalar> for #ident #ty_generics #where_clause { type Error = ::juniper::FieldError<#scalar>; fn from_input_value( value: &::juniper::InputValue<#scalar>, ) -> ::core::result::Result { let obj = value .to_object_value() .ok_or_else(|| ::juniper::FieldError::<#scalar>::from( ::std::format!("Expected input object, found: {}", value)) )?; ::core::result::Result::Ok(#ident { #( #fields )* }) } } } } /// Returns generated code implementing [`ToInputValue`] trait for this /// [GraphQL input object][0]. /// /// [`ToInputValue`]: juniper::ToInputValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_to_input_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let fields = self.fields.iter().filter_map(|f| { let ident = &f.ident; let name = &f.name; (!f.ignored).then(|| { quote! { (#name, ::juniper::ToInputValue::to_input_value(&self.#ident)) } }) }); quote! { #[automatically_derived] impl #impl_generics ::juniper::ToInputValue<#scalar> for #ident #ty_generics #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { ::juniper::InputValue::object( #[allow(deprecated)] ::std::array::IntoIter::new([#( #fields ),*]) .collect() ) } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL input object][0]. /// /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ident = &self.ident; let name = &self.name; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident #ty_generics #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[>::NAME]; } impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident #ty_generics #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this struct. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType #[must_use] fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ident = &self.ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ident #ty_generics } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } generics } } juniper_codegen-0.16.0/src/graphql_interface/attr.rs000064400000000000000000000272241046102023000206440ustar 00000000000000//! Code generation for `#[graphql_interface]` macro. use std::mem; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::common::{ diagnostic, field, parse::{self, TypeExt as _}, path_eq_single, rename, scalar, SpanContainer, }; use super::{enum_idents, Attr, Definition}; /// [`diagnostic::Scope`] of errors for `#[graphql_interface]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body.clone()) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_interface", "graphql"], ast.attrs); return expand_on_trait(trait_attrs, ast); } if let Ok(mut ast) = syn::parse2::(body) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_interface", "graphql"], ast.attrs); return expand_on_derive_input(trait_attrs, ast); } Err(syn::Error::new( Span::call_site(), "#[graphql_interface] attribute is applicable to trait and struct \ definitions only", )) } /// Expands `#[graphql_interface]` macro placed on the given trait definition. fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { let attr = Attr::from_attrs(["graphql_interface", "graphql"], &attrs)?; let trait_ident = &ast.ident; let trait_span = ast.span(); let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| trait_ident.unraw().to_string()) .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| trait_ident.span()), ); } let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let renaming = attr .rename_fields .as_deref() .copied() .unwrap_or(rename::Policy::CamelCase); let fields = ast .items .iter_mut() .filter_map(|item| { if let syn::TraitItem::Fn(m) = item { return parse_trait_method(m, &renaming); } None }) .collect::>(); diagnostic::abort_if_dirty(); if fields.is_empty() { ERR.emit_custom(trait_span, "must have at least one field"); } if !field::all_different(&fields) { ERR.emit_custom(trait_span, "must have a different name for each field"); } diagnostic::abort_if_dirty(); let context = attr .context .as_deref() .cloned() .or_else(|| { fields.iter().find_map(|f| { f.arguments.as_ref().and_then(|f| { f.iter() .find_map(field::MethodArgument::context_ty) .cloned() }) }) }) .unwrap_or_else(|| parse_quote! { () }); let (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); let generated_code = Definition { generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, name, description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for .into_iter() .map(SpanContainer::into_inner) .collect(), implements: attr .implements .into_iter() .map(SpanContainer::into_inner) .collect(), suppress_dead_code: None, src_intra_doc_link: format!("trait@{trait_ident}").into_boxed_str(), }; Ok(quote! { // Omit enforcing `# Errors` and `# Panics` sections in GraphQL descriptions. #[allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] #ast #generated_code }) } /// Parses a [`field::Definition`] from the given trait method definition. /// /// Returns [`None`] if the parsing fails, or the method field is ignored. #[must_use] fn parse_trait_method( method: &mut syn::TraitItemFn, renaming: &rename::Policy, ) -> Option { let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. method.attrs = mem::take(&mut method.attrs) .into_iter() .filter(|attr| !path_eq_single(attr.path(), "graphql")) .collect(); let attr = field::Attr::from_attrs("graphql", &method_attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } if method.default.is_some() { return err_default_impl_block(&method.default); } let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| method_ident.span()), ); return None; } let arguments = method .sig .inputs .iter_mut() .filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), }) .collect(); let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), }; ty.lifetimes_anonymized(); Some(field::Definition { name, ty, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), is_async: method.sig.asyncness.is_some(), }) } /// Expands `#[graphql_interface]` macro placed on the given struct. fn expand_on_derive_input( attrs: Vec, mut ast: syn::DeriveInput, ) -> syn::Result { let attr = Attr::from_attrs(["graphql_interface", "graphql"], &attrs)?; let struct_ident = &ast.ident; let struct_span = ast.span(); let data = match &mut ast.data { syn::Data::Struct(data) => data, syn::Data::Enum(_) | syn::Data::Union(_) => { return Err(ERR.custom_error( ast.span(), "#[graphql_interface] attribute is applicable to trait and \ struct definitions only", )); } }; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()) .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), ); } let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let renaming = attr .rename_fields .as_deref() .copied() .unwrap_or(rename::Policy::CamelCase); let fields = data .fields .iter_mut() .filter_map(|f| parse_struct_field(f, &renaming)) .collect::>(); diagnostic::abort_if_dirty(); if fields.is_empty() { ERR.emit_custom(struct_span, "must have at least one field"); } if !field::all_different(&fields) { ERR.emit_custom(struct_span, "must have a different name for each field"); } diagnostic::abort_if_dirty(); let context = attr .context .as_deref() .cloned() .or_else(|| { fields.iter().find_map(|f| { f.arguments.as_ref().and_then(|f| { f.iter() .find_map(field::MethodArgument::context_ty) .cloned() }) }) }) .unwrap_or_else(|| parse_quote! { () }); let (enum_ident, enum_alias_ident) = enum_idents(struct_ident, attr.r#enum.as_deref()); let generated_code = Definition { generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, name, description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for .into_iter() .map(SpanContainer::into_inner) .collect(), implements: attr .implements .into_iter() .map(SpanContainer::into_inner) .collect(), suppress_dead_code: None, src_intra_doc_link: format!("struct@{struct_ident}").into_boxed_str(), }; Ok(quote! { #[allow(dead_code)] #ast #generated_code }) } /// Parses a [`field::Definition`] from the given struct field definition. /// /// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] fn parse_struct_field( field: &mut syn::Field, renaming: &rename::Policy, ) -> Option { let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let field_attrs = field.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. field.attrs = mem::take(&mut field.attrs) .into_iter() .filter(|attr| !path_eq_single(attr.path(), "graphql")) .collect(); let attr = field::Attr::from_attrs("graphql", &field_attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| field_ident.span()), ); return None; } let mut ty = field.ty.clone(); ty.lifetimes_anonymized(); Some(field::Definition { name, ty, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: field_ident.clone(), arguments: None, has_receiver: false, is_async: false, }) } /// Emits "trait method can't have default implementation" [`syn::Error`] /// pointing to the given `span`. fn err_default_impl_block(span: &S) -> Option { ERR.emit_custom( span.span(), "trait method can't have default implementation", ); None } /// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. pub(crate) fn err_unnamed_field(span: &S) -> Option { ERR.emit_custom(span.span(), "expected named struct field"); None } juniper_codegen-0.16.0/src/graphql_interface/derive.rs000064400000000000000000000111371046102023000211440ustar 00000000000000//! Code generation for `#[derive(GraphQLInterface)]` macro. use proc_macro2::TokenStream; use quote::ToTokens as _; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::common::{diagnostic, field, parse::TypeExt as _, rename, scalar, SpanContainer}; use super::{attr::err_unnamed_field, enum_idents, Attr, Definition}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLInterface)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::InterfaceDerive; /// Expands `#[derive(GraphQLInterface)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = Attr::from_attrs("graphql", &ast.attrs)?; let data = if let syn::Data::Struct(data) = &ast.data { data } else { return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); }; let struct_ident = &ast.ident; let struct_span = ast.span(); let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()) .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), ); } let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let renaming = attr .rename_fields .as_deref() .copied() .unwrap_or(rename::Policy::CamelCase); let fields = data .fields .iter() .filter_map(|f| parse_field(f, &renaming)) .collect::>(); diagnostic::abort_if_dirty(); if fields.is_empty() { ERR.emit_custom(struct_span, "must have at least one field"); } if !field::all_different(&fields) { ERR.emit_custom(struct_span, "must have a different name for each field"); } diagnostic::abort_if_dirty(); let context = attr .context .as_deref() .cloned() .or_else(|| { fields.iter().find_map(|f| { f.arguments.as_ref().and_then(|f| { f.iter() .find_map(field::MethodArgument::context_ty) .cloned() }) }) }) .unwrap_or_else(|| parse_quote! { () }); let (enum_ident, enum_alias_ident) = enum_idents(struct_ident, attr.r#enum.as_deref()); Ok(Definition { generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, name, description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, implemented_for: attr .implemented_for .into_iter() .map(SpanContainer::into_inner) .collect(), implements: attr .implements .into_iter() .map(SpanContainer::into_inner) .collect(), suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), src_intra_doc_link: format!("struct@{struct_ident}").into_boxed_str(), } .into_token_stream()) } /// Parses a [`field::Definition`] from the given struct field definition. /// /// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option { let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let attr = field::Attr::from_attrs("graphql", &field.attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| field_ident.span()), ); return None; } let mut ty = field.ty.clone(); ty.lifetimes_anonymized(); Some(field::Definition { name, ty, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: field_ident.clone(), arguments: None, has_receiver: false, is_async: false, }) } juniper_codegen-0.16.0/src/graphql_interface/mod.rs000064400000000000000000001523431046102023000204520ustar 00000000000000//! Code generation for [GraphQL interface][1]. //! //! [1]: https://spec.graphql.org/October2021#sec-Interfaces pub mod attr; pub mod derive; use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, spanned::Spanned, token, visit::Visit, }; use crate::common::{ field, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, }, rename, scalar, AttrNames, Description, SpanContainer, }; /// Returns [`syn::Ident`]s for a generic enum deriving [`Clone`] and [`Copy`] /// on it and enum alias which generic arguments are filled with /// [GraphQL interface][1] implementers. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn enum_idents( trait_ident: &syn::Ident, alias_ident: Option<&syn::Ident>, ) -> (syn::Ident, syn::Ident) { let enum_alias_ident = alias_ident .cloned() .unwrap_or_else(|| format_ident!("{trait_ident}Value")); let enum_ident = alias_ident.map_or_else( || format_ident!("{trait_ident}ValueEnum"), |c| format_ident!("{c}Enum"), ); (enum_ident, enum_alias_ident) } /// Available arguments behind `#[graphql_interface]` attribute placed on a /// trait or struct definition, when generating code for [GraphQL interface][1] /// type. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[derive(Debug, Default)] struct Attr { /// Explicitly specified name of [GraphQL interface][1] type. /// /// If [`None`], then Rust trait name is used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces name: Option>, /// Explicitly specified [description][2] of [GraphQL interface][1] type. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified identifier of the type alias of Rust enum type /// behind the trait or struct, being an actual implementation of a /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces r#enum: Option>, /// Explicitly specified Rust types of [GraphQL objects][2] or /// [interfaces][1] implementing this [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Objects implemented_for: HashSet>, /// Explicitly specified [GraphQL interfaces, implemented][1] by this /// [GraphQL interface][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b implements: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Interfaces context: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to resolve this [GraphQL interface][1] type with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type, which, in turn, requires all [interface][1] /// implementers to be generic over any [`ScalarValue`] type too. That's why /// this type should be specified only if one of the implementers implements /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Interfaces scalar: Option>, /// Explicitly specified marker indicating that the Rust trait should be /// transformed into [`async_trait`]. /// /// If [`None`], then trait will be transformed into [`async_trait`] only if /// it contains async methods. asyncness: Option>, /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL interface][1] type. /// /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by /// default. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. is_internal: bool, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; out.context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "for" | "implementers" => { input.parse::()?; for impler in input.parse_maybe_wrapped_and_punctuated::< syn::TypePath, token::Bracket, token::Comma, >()? { let impler_span = impler.span(); out .implemented_for .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| err::dup_arg(impler_span))?; } } "impl" | "implements" => { input.parse::()?; for iface in input.parse_maybe_wrapped_and_punctuated::< syn::TypePath, token::Bracket, token::Comma, >()? { let iface_span = iface.span(); out .implements .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) .none_or_else(|_| err::dup_arg(iface_span))?; } } "enum" => { input.parse::()?; let alias = input.parse::()?; out.r#enum .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) .none_or_else(|_| err::dup_arg(&ident))? } "async" => { let span = ident.span(); out.asyncness .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } "rename_all" => { input.parse::()?; let val = input.parse::()?; out.rename_fields .replace(SpanContainer::new( ident.span(), Some(val.span()), val.try_into()?, )) .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`TraitAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), implements: try_merge_hashset!(implements: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) } /// Parses a [`TraitAttr`] from the provided multiple [`syn::Attribute`]s with /// the specified `names`, placed on a trait or struct definition. fn from_attrs(names: impl AttrNames, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(names, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces struct Definition { /// [`syn::Generics`] of the trait or struct describing the /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces generics: syn::Generics, /// [`syn::Visibility`] of the trait or struct describing the /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces vis: syn::Visibility, /// Name of the generic enum describing all [`implementers`]. It's generic /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. /// /// [`implementers`]: Self::implementers /// [`Debug`]: std::fmt::Debug enum_ident: syn::Ident, /// Name of the type alias for [`enum_ident`] with [`implementers`]. /// /// [`enum_ident`]: Self::enum_ident /// [`implementers`]: Self::implementers enum_alias_ident: syn::Ident, /// Name of this [GraphQL interface][0] in GraphQL schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Interfaces name: Box, /// Description of this [GraphQL interface][0] to put into GraphQL schema. /// /// [0]: https://spec.graphql.org/October2021#sec-Interfaces description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Interfaces context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Interfaces scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields fields: Vec, /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces implemented_for: Vec, /// [GraphQL interfaces implemented][1] by this [GraphQL interface][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Interfaces /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b implements: Vec, /// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't /// append `#[allow(dead_code)]` to the unused struct, representing /// [GraphQL interface][1]. We generate hacky `const` which doesn't actually /// use it, but suppresses this warning. /// /// [1]: https://spec.graphql.org/October2021#sec-Interfaces suppress_dead_code: Option<(syn::Ident, syn::Fields)>, /// Intra-doc link to the [`syn::Item`] defining this /// [GraphQL interface][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Interfaces src_intra_doc_link: Box, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); } } impl Definition { /// Generates enum describing all the [`implementers`]. /// /// [`implementers`]: Self::implementers #[must_use] fn generate_enum_tokens(&self) -> TokenStream { let vis = &self.vis; let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; let variant_gens_pars = (0..self.implemented_for.len()).map::(|id| { let par = format_ident!("__I{id}"); parse_quote! { #par } }); let variants_idents = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); let interface_gens = &self.generics; let (interface_impl_gens, interface_ty_gens, interface_where_clause) = self.generics.split_for_impl(); let (interface_gens_lifetimes, interface_gens_tys) = interface_gens .params .clone() .into_iter() .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); let enum_gens = { let mut enum_gens = interface_gens.clone(); enum_gens.params = interface_gens_lifetimes.clone(); enum_gens.params.extend(variant_gens_pars.clone()); enum_gens.params.extend(interface_gens_tys.clone()); enum_gens }; let enum_alias_gens = { let mut enum_alias_gens = interface_gens.clone(); enum_alias_gens.move_bounds_to_where_clause(); enum_alias_gens }; let enum_to_alias_gens = { interface_gens_lifetimes .into_iter() .map(|par| match par { syn::GenericParam::Lifetime(def) => { let lifetime = &def.lifetime; quote! { #lifetime } } rest => quote! { #rest }, }) .chain(self.implemented_for.iter().map(ToTokens::to_token_stream)) .chain(interface_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; quote! { #par_ident } } rest => quote! { #rest }, })) }; let enum_doc = format!( "Enum building an opaque value represented by [`{}`]({}) \ [GraphQL interface][0].\ \n\n\ [0]: https://spec.graphql.org/October2021#sec-Interfaces", self.name, self.src_intra_doc_link, ); let enum_alias_doc = format!( "Opaque value represented by [`{}`]({}) [GraphQL interface][0].\ \n\n\ [0]: https://spec.graphql.org/October2021#sec-Interfaces", self.name, self.src_intra_doc_link, ); let phantom_variant = self .has_phantom_variant() .then(|| { let phantom_params = interface_gens.params.iter().filter_map(|p| { let ty = match p { syn::GenericParam::Type(ty) => { let ident = &ty.ident; quote! { #ident } } syn::GenericParam::Lifetime(lt) => { let lifetime = <.lifetime; quote! { &#lifetime () } } syn::GenericParam::Const(_) => return None, }; Some(quote! { ::core::marker::PhantomData< ::core::sync::atomic::AtomicPtr> > }) }); quote! { __Phantom(#(#phantom_params),*) } }) .into_iter(); let from_impls = self .implemented_for .iter() .zip(variants_idents.clone()) .map(|(ty, ident)| { quote! { #[automatically_derived] impl #interface_impl_gens ::core::convert::From<#ty> for #alias_ident #interface_ty_gens #interface_where_clause { fn from(v: #ty) -> Self { Self::#ident(v) } } } }); quote! { #[automatically_derived] #[derive(::core::clone::Clone, ::core::marker::Copy, ::core::fmt::Debug)] #[doc = #enum_doc] #vis enum #enum_ident #enum_gens { #( #[doc(hidden)] #variants_idents(#variant_gens_pars), )* #( #[doc(hidden)] #phantom_variant, )* } #[automatically_derived] #[doc = #enum_alias_doc] #vis type #alias_ident #enum_alias_gens = #enum_ident<#( #enum_to_alias_gens ),*>; #( #from_impls )* } } /// Returns generated code implementing [`GraphQLInterface`] trait for this /// [GraphQL interface][1]. /// /// [`GraphQLInterface`]: juniper::GraphQLInterface /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let gens = self.impl_generics(false); let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| { let const_gens = self.const_trait_generics(); let fields = fields.iter().map(|f| &f.ident); quote! {{ const SUPPRESS_DEAD_CODE: () = { let none = ::core::option::Option::<#ident #const_gens>::None; match none { ::core::option::Option::Some(unreachable) => { #( let _ = unreachable.#fields; )* } ::core::option::Option::None => {} } }; let _ = SUPPRESS_DEAD_CODE; }} }); let implemented_for = &self.implemented_for; let all_impled_for_unique = (implemented_for.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } }); let mark_object_or_interface = self.implemented_for.iter().map(|impl_for| { quote_spanned! { impl_for.span() => trait GraphQLObjectOrInterface { fn mark(); } { struct Object; impl GraphQLObjectOrInterface for T where S: ::juniper::ScalarValue, T: ::juniper::marker::GraphQLObject, { fn mark() { >::mark() } } } { struct Interface; impl GraphQLObjectOrInterface for T where S: ::juniper::ScalarValue, T: ::juniper::marker::GraphQLInterface, { fn mark() { >::mark() } } } <#impl_for as GraphQLObjectOrInterface<#scalar, _>>::mark(); } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #ty_generics #where_clause { fn mark() { #suppress_dead_code #all_impled_for_unique #( { #mark_object_or_interface } )* } } } } /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL interface][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = &self.scalar.default_ty(); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let ty_const_generics = self.const_trait_generics(); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); let is_output = self.implemented_for.iter().map(|impler| { quote_spanned! { impler.span() => <#impler as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); let const_impl_for = self.implemented_for.iter().cloned().map(|mut ty| { generics.replace_type_path_with_defaults(&mut ty); ty }); let const_implements = self .implements .iter() .cloned() .map(|mut ty| { generics.replace_type_path_with_defaults(&mut ty); ty }) .collect::>(); let transitive_checks = const_impl_for.clone().map(|const_impl_for| { quote_spanned! { const_impl_for.span() => ::juniper::assert_transitive_impls!( #const_scalar, #ty #ty_const_generics, #const_impl_for, #( #const_implements ),* ); } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #ty_generics #where_clause { fn mark() { #( #fields_marks )* #( #is_output )* ::juniper::assert_interfaces_impls!( #const_scalar, #ty #ty_const_generics, #( #const_impl_for ),* ); ::juniper::assert_implemented_for!( #const_scalar, #ty #ty_const_generics, #( #const_implements ),* ); #( #transitive_checks )* } } } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let name = &self.name; let description = &self.description; // Sorting is required to preserve/guarantee the order of implementers registered in schema. let mut implemented_for = self.implemented_for.clone(); implemented_for.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. let mut implements = self.implements.clone(); implements.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); let impl_interfaces = (!implements.is_empty()).then(|| { quote! { .interfaces(&[ #( registry.get_type::<#implements>(info), )* ]) } }); let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty #ty_generics #where_clause { fn name( _ : &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { // Ensure all implementer types are registered. #( let _ = registry.get_type::<#implemented_for>(info); )* let fields = [ #( #fields_meta, )* ]; registry.build_interface_type::<#ty #ty_generics>(info, &fields) #description #impl_interfaces .into_meta() } } } } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL interface][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; Some(quote! { #name => { ::juniper::macros::reflect::Field::< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } }) }); let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); let downcast_check = self.method_concrete_type_name_tokens(); let downcast = self.method_resolve_into_type_tokens(); quote! { #[allow(deprecated)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #ty_generics #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } fn resolve_field( &self, info: &Self::TypeInfo, field: &::core::primitive::str, args: &::juniper::Arguments<'_, #scalar>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* _ => #no_field_err, } } fn concrete_type_name( &self, context: &Self::Context, info: &Self::TypeInfo, ) -> ::std::string::String { #downcast_check } fn resolve_into_type( &self, info: &Self::TypeInfo, type_name: &::core::primitive::str, _: ::core::option::Option<&[::juniper::Selection<'_, #scalar>]>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #downcast } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL interface][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let trait_name = &self.name; let scalar = &self.scalar; let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; quote! { #name => { ::juniper::macros::reflect::AsyncField::< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } }); let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); let downcast = self.method_resolve_into_type_async_tokens(); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #ty_generics #where_clause { fn resolve_field_async<'b>( &'b self, info: &'b Self::TypeInfo, field: &'b ::core::primitive::str, args: &'b ::juniper::Arguments<'_, #scalar>, executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match field { #( #fields_resolvers )* _ => ::std::boxed::Box::pin(async move { #no_field_err }), } } fn resolve_into_type_async<'b>( &'b self, info: &'b Self::TypeInfo, type_name: &::core::primitive::str, _: ::core::option::Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { #downcast } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`Fields`]: juniper::macros::reflect::Fields /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let implemented_for = &self.implemented_for; let implements = &self.implements; let scalar = &self.scalar; let name = &self.name; let fields = self.fields.iter().map(|f| &f.name); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #ty_generics #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, #( <#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),* ]; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Implements<#scalar> for #ty #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[#( <#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),*]; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #ty_generics #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Fields<#scalar> for #ty #ty_generics #where_clause { const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; } } } /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// /// [`FieldMeta`]: juniper::macros::reflect::FieldMeta /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_field_meta_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); self.fields .iter() .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments .iter() .flat_map(|vec| vec.iter()) .filter_map(|arg| match arg { field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), _ => None, }) .unzip(); quote! { #[allow(non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::FieldMeta< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty #ty_generics #where_clause { type Context = #context; type TypeInfo = (); const TYPE: ::juniper::macros::reflect::Type = <#return_ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; const SUB_TYPES: ::juniper::macros::reflect::Types = <#return_ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; const WRAPPED_VALUE: ::juniper::macros::reflect::WrappedValue = <#return_ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( ::juniper::macros::reflect::Name, ::juniper::macros::reflect::Type, ::juniper::macros::reflect::WrappedValue, )] = &[#( ( #args_names, <#args_tys as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, <#args_tys as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, ) ),*]; } } }) .collect() } /// Returns generated code implementing [`Field`] trait for each field of /// this [GraphQL interface][1]. /// /// [`Field`]: juniper::macros::reflect::Field /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let const_implemented_for = self .implemented_for .iter() .cloned() .map(|mut impl_for| { generics.replace_type_path_with_defaults(&mut impl_for); impl_for }) .collect::>(); let implemented_for_idents = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); self.fields .iter() .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); let const_ty_generics = self.const_trait_generics(); let unreachable_arm = (self.implemented_for.is_empty() || !self.generics.params.is_empty()) .then(|| { quote! { _ => unreachable!() } }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Field< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty #ty_generics #where_clause { fn call( &self, info: &Self::TypeInfo, args: &::juniper::Arguments<'_, #scalar>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty #const_ty_generics, #const_implemented_for, #const_scalar, #field_name, ); <_ as ::juniper::macros::reflect::Field::< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) } )* #unreachable_arm } } } } }) .collect() } /// Returns generated code implementing [`AsyncField`] trait for each field /// of this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflect::AsyncField /// [1]: https://spec.graphql.org/October2021#sec-Interfaces fn impl_async_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let const_implemented_for = self .implemented_for .iter() .cloned() .map(|mut impl_for| { generics.replace_type_path_with_defaults(&mut impl_for); impl_for }) .collect::>(); let implemented_for_idents = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); self.fields .iter() .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); let const_ty_generics = self.const_trait_generics(); let unreachable_arm = (self.implemented_for.is_empty() || !self.generics.params.is_empty()) .then(|| { quote! { _ => unreachable!() } }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::AsyncField< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty #ty_generics #where_clause { fn call<'b>( &'b self, info: &'b Self::TypeInfo, args: &'b ::juniper::Arguments<'_, #scalar>, executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty #const_ty_generics, #const_implemented_for, #const_scalar, #field_name, ); <_ as ::juniper::macros::reflect::AsyncField< #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) } )* #unreachable_arm } } } } }) .collect() } /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] /// method, which returns name of the underlying [`implementers`][1] GraphQL /// type contained in this enum. /// /// [0]: juniper::GraphQLValue::concrete_type_name /// [1]: Self::implementers #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; let match_arms = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) .map(|(ident, ty)| { quote! { Self::#ident(v) => < #ty as ::juniper::GraphQLValue<#scalar> >::concrete_type_name(v, context, info), } }); let non_exhaustive_match_arm = (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); quote! { match self { #( #match_arms )* #non_exhaustive_match_arm } } } /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which /// downcasts this enum into its underlying [`implementers`][1] type /// asynchronously. /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { let fut = ::juniper::futures::future::ready(v); #resolving_code } } }) }); let non_exhaustive_match_arm = (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); quote! { match self { #( #match_arms )* #non_exhaustive_match_arm } } } /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] /// method, which resolves this enum into its underlying /// [`implementers`][1] type synchronously. /// /// [0]: juniper::GraphQLValue::resolve_into_type /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, } }) }); let non_exhaustive_match_arm = (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); quote! { match self { #( #match_arms )* #non_exhaustive_match_arm } } } /// Returns trait generics replaced with the default values for usage in a /// `const` context. #[must_use] fn const_trait_generics(&self) -> syn::PathArguments { struct GenericsForConst(syn::AngleBracketedGenericArguments); impl Visit<'_> for GenericsForConst { fn visit_generic_param(&mut self, param: &syn::GenericParam) { let arg = match param { syn::GenericParam::Lifetime(_) => parse_quote! { 'static }, syn::GenericParam::Type(ty) => { if ty.default.is_none() { parse_quote! { ::juniper::DefaultScalarValue } } else { return; } } syn::GenericParam::Const(c) => { if c.default.is_none() { // This hack works because only `min_const_generics` // are enabled for now. // TODO: Replace this once full `const_generics` are // available. // Maybe with `<_ as Default>::default()`? parse_quote!({ 0_u8 as _ }) } else { return; } } }; self.0.args.push(arg) } } let mut visitor = GenericsForConst(parse_quote!( <> )); visitor.visit_generics(&self.generics); syn::PathArguments::AngleBracketed(visitor.0) } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this enum. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType #[must_use] fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ty = &self.enum_alias_ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty #ty_generics } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } generics } /// Indicates whether this enum has non-exhaustive phantom variant to hold /// type parameters. #[must_use] fn has_phantom_variant(&self) -> bool { !self.generics.params.is_empty() } } juniper_codegen-0.16.0/src/graphql_object/attr.rs000064400000000000000000000170001046102023000201410ustar 00000000000000//! Code generation for `#[graphql_object]` macro. use std::{any::TypeId, marker::PhantomData, mem}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::common::{ diagnostic, field, parse::{self, TypeExt as _}, path_eq_single, rename, scalar, SpanContainer, }; use super::{Attr, Definition, Query}; /// [`diagnostic::Scope`] of errors for `#[graphql_object]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ObjectAttr; /// Expands `#[graphql_object]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body) { if ast.trait_.is_none() { let impl_attrs = parse::attr::unite(("graphql_object", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_object", "graphql"], ast.attrs); return expand_on_impl::( Attr::from_attrs(["graphql_object", "graphql"], &impl_attrs)?, ast, ); } } Err(syn::Error::new( Span::call_site(), "#[graphql_object] attribute is applicable to non-trait `impl` blocks only", )) } /// Expands `#[graphql_object]` macro placed on an implementation block. pub(crate) fn expand_on_impl( attr: Attr, mut ast: syn::ItemImpl, ) -> syn::Result where Definition: ToTokens, Operation: 'static, { let type_span = ast.self_ty.span(); let type_ident = ast.self_ty.topmost_ident().ok_or_else(|| { ERR.custom_error(type_span, "could not determine ident for the `impl` type") })?; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| type_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| type_ident.span()), ); } let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let renaming = attr .rename_fields .as_deref() .copied() .unwrap_or(rename::Policy::CamelCase); let async_only = TypeId::of::() != TypeId::of::(); let fields: Vec<_> = ast .items .iter_mut() .filter_map(|item| { if let syn::ImplItem::Fn(m) = item { parse_field(m, async_only, &renaming) } else { None } }) .collect(); diagnostic::abort_if_dirty(); if fields.is_empty() { ERR.emit_custom(type_span, "must have at least one field"); } if !field::all_different(&fields) { ERR.emit_custom(type_span, "must have a different name for each field"); } diagnostic::abort_if_dirty(); let context = attr .context .as_deref() .cloned() .or_else(|| { fields.iter().find_map(|f| { f.arguments.as_ref().and_then(|f| { f.iter() .find_map(field::MethodArgument::context_ty) .cloned() }) }) }) .unwrap_or_else(|| parse_quote! { () }); let generated_code = Definition:: { name, ty: ast.self_ty.unparenthesized().clone(), generics: ast.generics.clone(), description: attr.description.map(SpanContainer::into_inner), context, scalar, fields, interfaces: attr .interfaces .iter() .map(|ty| ty.as_ref().clone()) .collect(), _operation: PhantomData, }; Ok(quote! { // Omit enforcing `# Errors` and `# Panics` sections in GraphQL descriptions. #[allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] #ast #generated_code }) } /// Parses a [`field::Definition`] from the given Rust [`syn::ImplItemFn`]. /// /// Returns [`None`] if parsing fails, or the method field is ignored. #[must_use] fn parse_field( method: &mut syn::ImplItemFn, async_only: bool, renaming: &rename::Policy, ) -> Option { let method_attrs = method.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. method.attrs = mem::take(&mut method.attrs) .into_iter() .filter(|attr| !path_eq_single(attr.path(), "graphql")) .collect(); let attr = field::Attr::from_attrs("graphql", &method_attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } if async_only && method.sig.asyncness.is_none() { return err_no_sync_resolvers(&method.sig); } let method_ident = &method.sig.ident; let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| method_ident.span()), ); return None; } let arguments = { if let Some(arg) = method.sig.inputs.first() { match arg { syn::FnArg::Receiver(rcv) => { if rcv.reference.is_none() || rcv.mutability.is_some() { return err_invalid_method_receiver(rcv); } } syn::FnArg::Typed(arg) => { if let syn::Pat::Ident(a) = &*arg.pat { if a.ident == "self" { return err_invalid_method_receiver(arg); } } } } } method .sig .inputs .iter_mut() .filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), }) .collect() }; let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), }; ty.lifetimes_anonymized(); Some(field::Definition { name, ty, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: method_ident.clone(), arguments: Some(arguments), has_receiver: method.sig.receiver().is_some(), is_async: method.sig.asyncness.is_some(), }) } /// Emits "invalid method receiver" [`syn::Error`] pointing to the given `span`. #[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.emit_custom( span.span(), "method should have a shared reference receiver `&self`, or no receiver at all", ); None } /// Emits "synchronous resolvers are not supported" [`syn::Error`] pointing to /// the given `span`. #[must_use] fn err_no_sync_resolvers(span: &S) -> Option { ERR.custom(span.span(), "synchronous resolvers are not supported") .note("Specify that this function is async: `async fn foo()`") .emit(); None } juniper_codegen-0.16.0/src/graphql_object/derive.rs000064400000000000000000000106671046102023000204610ustar 00000000000000//! Code generation for `#[derive(GraphQLObject)]` macro. use std::marker::PhantomData; use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::common::{ diagnostic::{self, ResultExt as _}, field, parse::TypeExt as _, rename, scalar, SpanContainer, }; use super::{Attr, Definition, Query}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLObject)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ObjectDerive; /// Expands `#[derive(GraphQLObject)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input).unwrap_or_abort(); match &ast.data { syn::Data::Struct(_) => expand_struct(ast), _ => Err(ERR.custom_error(ast.span(), "can only be derived for structs")), } .map(ToTokens::into_token_stream) } /// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a /// Rust struct. fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { let attr = Attr::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); let struct_ident = ast.ident; let (_, struct_generics, _) = ast.generics.split_for_impl(); let ty = parse_quote! { #struct_ident #struct_generics }; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), ); } let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diagnostic::abort_if_dirty(); let renaming = attr .rename_fields .as_deref() .copied() .unwrap_or(rename::Policy::CamelCase); let mut fields = vec![]; if let syn::Data::Struct(data) = &ast.data { if let syn::Fields::Named(fs) = &data.fields { fields = fs .named .iter() .filter_map(|f| parse_field(f, &renaming)) .collect(); } else { ERR.emit_custom(struct_span, "only named fields are allowed"); } } diagnostic::abort_if_dirty(); if fields.is_empty() { ERR.emit_custom(struct_span, "must have at least one field"); } if !field::all_different(&fields) { ERR.emit_custom(struct_span, "must have a different name for each field"); } diagnostic::abort_if_dirty(); Ok(Definition { name, ty, generics: ast.generics, description: attr.description.map(SpanContainer::into_inner), context: attr .context .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar, fields, interfaces: attr .interfaces .iter() .map(|ty| ty.as_ref().clone()) .collect(), _operation: PhantomData, }) } /// Parses a [`field::Definition`] from the given Rust struct [`syn::Field`]. /// /// Returns [`None`] if parsing fails, or the struct field is ignored. #[must_use] fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option { let attr = field::Attr::from_attrs("graphql", &field.attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } let field_ident = field.ident.as_ref().unwrap(); let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| field_ident.span()), ); return None; } let mut ty = field.ty.unparenthesized().clone(); ty.lifetimes_anonymized(); Some(field::Definition { name, ty, description: attr.description.map(SpanContainer::into_inner), deprecated: attr.deprecated.map(SpanContainer::into_inner), ident: field_ident.clone(), arguments: None, has_receiver: false, is_async: false, }) } juniper_codegen-0.16.0/src/graphql_object/mod.rs000064400000000000000000001015341046102023000177540ustar 00000000000000//! Code generation for [GraphQL object][1]. //! //! [1]: https://spec.graphql.org/October2021#sec-Objects pub mod attr; pub mod derive; use std::{any::TypeId, collections::HashSet, marker::PhantomData}; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, }; use crate::common::{ field, filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, TypeExt, }, rename, scalar, AttrNames, Description, SpanContainer, }; /// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute /// when generating code for [GraphQL object][1] type. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects #[derive(Debug, Default)] pub(crate) struct Attr { /// Explicitly specified name of this [GraphQL object][1] type. /// /// If [`None`], then Rust type name is used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL object][1] type. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL object][1] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) context: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to use for resolving this [GraphQL object][1] type with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type, which, in turn, requires all [object][1] fields to /// be generic over any [`ScalarValue`] type too. That's why this type /// should be specified only if one of the variants implements /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) scalar: Option>, /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub(crate) interfaces: HashSet>, /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL object][1] type. /// /// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by /// default. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. pub(crate) is_internal: bool, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; out.context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "impl" | "implements" | "interfaces" => { input.parse::()?; for iface in input.parse_maybe_wrapped_and_punctuated::< syn::Type, token::Bracket, token::Comma, >()? { let iface_span = iface.span(); out .interfaces .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) .none_or_else(|_| err::dup_arg(iface_span))?; } } "rename_all" => { input.parse::()?; let val = input.parse::()?; out.rename_fields .replace(SpanContainer::new( ident.span(), Some(val.span()), val.try_into()?, )) .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), interfaces: try_merge_hashset!(interfaces: self, another => span_joined), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) } /// Parses an [`Attr`] from the provided multiple [`syn::Attribute`]s with /// the specified `names`, placed on a struct or impl block definition. pub(crate) fn from_attrs(names: impl AttrNames, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(names, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// Definition of [GraphQL object][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects #[derive(Debug)] pub(crate) struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) name: String, /// Rust type that this [GraphQL object][1] is represented with. /// /// It should contain all its generics, if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) ty: syn::Type, /// Generics of the Rust type that this [GraphQL object][1] is implemented /// for. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) generics: syn::Generics, /// Description of this [GraphQL object][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL object][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL object][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Objects pub(crate) scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields pub(crate) fields: Vec, /// [GraphQL interfaces][2] implemented by this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects /// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub(crate) interfaces: HashSet, /// [GraphQL operation][1] this [`Definition`] should generate code for. /// /// Either [GraphQL query][2] or [GraphQL subscription][3]. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Operations /// [2]: https://spec.graphql.org/October2021#sec-Query /// [3]: https://spec.graphql.org/October2021#sec-Subscription pub(crate) _operation: PhantomData>, } impl Definition { /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`] /// trait (and similar) implementation of this [GraphQL object][1]. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { let mut lifetimes = vec![]; // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut ty = self.ty.clone(); ty.lifetimes_iter_mut(&mut |lt| { let ident = lt.ident.unraw(); lt.ident = format_ident!("__fa__{ident}"); lifetimes.push(lt.clone()); }); quote! { for<#( #lifetimes ),*> #ty } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } let (impl_generics, _, where_clause) = generics.split_for_impl(); (quote! { #impl_generics }, where_clause.cloned()) } /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL object][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let coerce_result = TypeId::of::() != TypeId::of::(); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(coerce_result, scalar)); let interface_tys = self.interfaces.iter(); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { fn mark() { #( #fields_marks )* #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], /// [`WrappedType`] and [`Fields`] traits for this [GraphQL object][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`Fields`]: juniper::macros::reflect::Fields /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let fields = self.fields.iter().map(|f| &f.name); let interfaces = self.interfaces.iter(); quote! { #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[>::NAME]; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Implements<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[#(<#interfaces as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*]; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Fields<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; } } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] pub(crate) fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; let description = &self.description; let extract_stream_type = TypeId::of::() != TypeId::of::(); let fields_meta = self .fields .iter() .map(|f| f.method_meta_tokens(extract_stream_type.then_some(scalar))); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. let mut interface_tys: Vec<_> = self.interfaces.iter().collect(); interface_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); let interfaces = (!interface_tys.is_empty()).then(|| { quote! { .interfaces(&[ #( registry.get_type::<#interface_tys>(info), )* ]) } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name( _ : &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { let fields = [ #( #fields_meta, )* ]; registry.build_object_type::<#ty>(info, &fields) #description #interfaces .into_meta() } } } } } /// [GraphQL query operation][2] of the [`Definition`] to generate code for. /// /// [2]: https://spec.graphql.org/October2021#sec-Query struct Query; impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_graphql_object_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing [`GraphQLObject`] trait for this /// [GraphQL object][1]. /// /// [`GraphQLObject`]: juniper::GraphQLObject /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let interface_tys = self.interfaces.iter(); let generics = { let mut generics = self.generics.clone(); if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }) } generics }; let const_interface_tys = interface_tys.clone().cloned().map(|mut ty| { generics.replace_type_with_defaults(&mut ty); ty }); // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); //let all_interfaces_unique = (interface_tys.len() > 1).then(|| { // quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); } //}); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #where_clause { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* ::juniper::assert_implemented_for!( #const_scalar, #ty, #(#const_interface_tys),* ); } } } } /// Returns generated code implementing [`FieldMeta`] traits for each field /// of this [GraphQL object][1]. /// /// [`FieldMeta`]: juniper::FieldMeta /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_field_meta_tokens(&self) -> TokenStream { let impl_ty = &self.ty; let scalar = &self.scalar; let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(false); self.fields .iter() .map(|field| { let (name, ty) = (&field.name, field.ty.clone()); let arguments = field .arguments .as_ref() .iter() .flat_map(|vec| vec.iter()) .filter_map(|arg| match arg { field::MethodArgument::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); Some(quote! {( #name, <#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, <#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, )}) } field::MethodArgument::Executor | field::MethodArgument::Context(_) => None, }) .collect::>(); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::FieldMeta< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } > for #impl_ty #where_clause { type Context = #context; type TypeInfo = (); const TYPE: ::juniper::macros::reflect::Type = <#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; const SUB_TYPES: ::juniper::macros::reflect::Types = <#ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; const WRAPPED_VALUE: juniper::macros::reflect::WrappedValue = <#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( ::juniper::macros::reflect::Name, ::juniper::macros::reflect::Type, ::juniper::macros::reflect::WrappedValue, )] = &[#(#arguments,)*]; } } }) .collect() } /// Returns generated code implementing [`Field`] trait for each field of /// this [GraphQL object][1]. /// /// [`Field`]: juniper::Field /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_field_tokens(&self) -> TokenStream { let (impl_ty, scalar) = (&self.ty, &self.scalar); let (impl_generics, where_clause) = self.impl_generics(false); self.fields .iter() .map(|field| { let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); let resolve = if field.is_async { quote! { ::core::panic!( "Tried to resolve async field `{}` on type `{}` with a sync resolver", #name, >::NAME, ); } } else { let res = if field.is_method() { let args = field .arguments .as_ref() .unwrap() .iter() .map(|arg| arg.method_resolve_field_tokens(scalar, false)); let rcv = field.has_receiver.then(|| { quote! { self, } }); quote! { Self::#ident(#rcv #( #args ),*) } } else { res_ty = parse_quote! { _ }; quote! { &self.#ident } }; let resolving_code = gen::sync_resolving_code(); quote! { let res: #res_ty = #res; #resolving_code } }; quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::Field< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } > for #impl_ty #where_clause { fn call( &self, info: &Self::TypeInfo, args: &::juniper::Arguments<'_, #scalar>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #resolve } } } }) .collect() } /// Returns generated code implementing [`AsyncField`] trait for each field /// of this [GraphQL object][1]. /// /// [`AsyncField`]: juniper::AsyncField /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_async_field_tokens(&self) -> TokenStream { let (impl_ty, scalar) = (&self.ty, &self.scalar); let (impl_generics, where_clause) = self.impl_generics(true); self.fields .iter() .map(|field| { let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); let mut res = if field.is_method() { let args = field .arguments .as_ref() .unwrap() .iter() .map(|arg| arg.method_resolve_field_tokens(scalar, true)); let rcv = field.has_receiver.then(|| { quote! { self, } }); quote! { Self::#ident(#rcv #( #args ),*) } } else { res_ty = parse_quote! { _ }; quote! { &self.#ident } }; if !field.is_async { res = quote! { ::juniper::futures::future::ready(#res) }; } let resolving_code = gen::async_resolving_code(Some(&res_ty)); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::AsyncField< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } > for #impl_ty #where_clause { fn call<'b>( &'b self, info: &'b Self::TypeInfo, args: &'b ::juniper::Arguments<'_, #scalar>, executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let fut = #res; #resolving_code } } } }) .collect() } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); let name = &self.name; let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; quote! { #name => { ::juniper::macros::reflect::Field::< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } }); let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); quote! { #[allow(deprecated)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } fn resolve_field( &self, info: &Self::TypeInfo, field: &::core::primitive::str, args: &::juniper::Arguments<'_, #scalar>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* _ => #no_field_err, } } fn concrete_type_name( &self, _: &Self::Context, _: &Self::TypeInfo, ) -> ::std::string::String { #name.into() } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL object][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/October2021#sec-Objects #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; quote! { #name => { ::juniper::macros::reflect::AsyncField::< #scalar, { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } }); let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_field_async<'b>( &'b self, info: &'b Self::TypeInfo, field: &'b ::core::primitive::str, args: &'b ::juniper::Arguments<'_, #scalar>, executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match field { #( #fields_resolvers )* _ => ::std::boxed::Box::pin(async move { #no_field_err }), } } } } } } juniper_codegen-0.16.0/src/graphql_scalar/attr.rs000064400000000000000000000112531046102023000201440ustar 00000000000000//! Code generation for `#[graphql_scalar]` macro. use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{parse_quote, spanned::Spanned}; use crate::common::{diagnostic, parse, scalar, SpanContainer}; use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken, TypeOrIdent}; /// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr; /// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body.clone()) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_scalar", "graphql"], ast.attrs); return expand_on_type_alias(attrs, ast); } else if let Ok(mut ast) = syn::parse2::(body) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_scalar", "graphql"], ast.attrs); return expand_on_derive_input(attrs, ast); } Err(syn::Error::new( Span::call_site(), "#[graphql_scalar] attribute is applicable to type aliases, structs, \ enums and unions only", )) } /// Expands `#[graphql_scalar]` macro placed on a type alias. fn expand_on_type_alias( attrs: Vec, ast: syn::ItemType, ) -> syn::Result { let attr = Attr::from_attrs(["graphql_scalar", "graphql"], &attrs)?; if attr.transparent { return Err(ERR.custom_error( ast.span(), "`transparent` attribute argument isn't applicable to type aliases", )); } let methods = parse_type_alias_methods(&ast, &attr)?; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let def = Definition { ty: TypeOrIdent::Type(ast.ty.clone()), where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), generics: ast.generics.clone(), methods, name: attr .name .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, }; Ok(quote! { #ast #def }) } /// Expands `#[graphql_scalar]` macro placed on a struct, enum or union. fn expand_on_derive_input( attrs: Vec, ast: syn::DeriveInput, ) -> syn::Result { let attr = Attr::from_attrs(["graphql_scalar", "graphql"], &attrs)?; let methods = parse_derived_methods(&ast, &attr)?; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let def = Definition { ty: TypeOrIdent::Ident(ast.ident.clone()), where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), generics: ast.generics.clone(), methods, name: attr .name .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, }; Ok(quote! { #ast #def }) } /// Parses [`Methods`] from the provided [`Attr`] for the specified type alias. fn parse_type_alias_methods(ast: &syn::ItemType, attr: &Attr) -> syn::Result { match ( attr.to_output.as_deref().cloned(), attr.from_input.as_deref().cloned(), attr.parse_token.as_deref().cloned(), attr.with.as_deref().cloned(), ) { (Some(to_output), Some(from_input), Some(parse_token), None) => Ok(Methods::Custom { to_output, from_input, parse_token, }), (to_output, from_input, parse_token, Some(module)) => Ok(Methods::Custom { to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), parse_token: parse_token .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), }), _ => Err(ERR.custom_error( ast.span(), "all the resolvers have to be provided via `with` attribute \ argument or a combination of `to_output_with`, `from_input_with`, \ `parse_token_with`/`parse_token` attribute arguments", )), } } juniper_codegen-0.16.0/src/graphql_scalar/derive.rs000064400000000000000000000113351046102023000204510ustar 00000000000000//! Code generation for `#[derive(GraphQLScalar)]` macro. use proc_macro2::TokenStream; use quote::ToTokens; use syn::{parse_quote, spanned::Spanned}; use crate::common::{diagnostic, scalar, SpanContainer}; use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent}; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive; /// Expands `#[derive(GraphQLScalar)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = Attr::from_attrs("graphql", &ast.attrs)?; let methods = parse_derived_methods(&ast, &attr)?; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); Ok(Definition { ty: TypeOrIdent::Ident(ast.ident.clone()), where_clause: attr .where_clause .map_or_else(Vec::new, |cl| cl.into_inner()), generics: ast.generics.clone(), methods, name: attr .name .map(SpanContainer::into_inner) .unwrap_or_else(|| ast.ident.to_string()), description: attr.description.map(SpanContainer::into_inner), specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner), scalar, } .to_token_stream()) } /// Parses [`Methods`] from the provided [`Attr`] for the specified /// [`syn::DeriveInput`]. pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn::Result { match ( attr.to_output.as_deref().cloned(), attr.from_input.as_deref().cloned(), attr.parse_token.as_deref().cloned(), attr.with.as_deref().cloned(), attr.transparent, ) { (Some(to_output), Some(from_input), Some(parse_token), None, false) => { Ok(Methods::Custom { to_output, from_input, parse_token, }) } (to_output, from_input, parse_token, module, false) => { let module = module.unwrap_or_else(|| parse_quote! { Self }); Ok(Methods::Custom { to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), parse_token: parse_token .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), }) } (to_output, from_input, parse_token, None, true) => { let data = if let syn::Data::Struct(data) = &ast.data { data } else { return Err(ERR.custom_error( ast.span(), "`transparent` attribute argument requires exactly 1 field", )); }; let field = match &data.fields { syn::Fields::Unit => Err(ERR.custom_error( ast.span(), "`transparent` attribute argument requires exactly 1 field", )), syn::Fields::Unnamed(fields) => fields .unnamed .first() .filter(|_| fields.unnamed.len() == 1) .cloned() .map(Field::Unnamed) .ok_or_else(|| { ERR.custom_error( ast.span(), "`transparent` attribute argument requires \ exactly 1 field", ) }), syn::Fields::Named(fields) => fields .named .first() .filter(|_| fields.named.len() == 1) .cloned() .map(Field::Named) .ok_or_else(|| { ERR.custom_error( ast.span(), "`transparent` attribute argument requires \ exactly 1 field", ) }), }?; Ok(Methods::Delegated { to_output, from_input, parse_token, field: Box::new(field), }) } (_, _, _, Some(module), true) => Err(ERR.custom_error( module.span(), "`with = ` attribute argument cannot be combined with \ `transparent`. \ You can specify custom resolvers with `to_output_with`, \ `from_input_with`, `parse_token`/`parse_token_with` attribute \ arguments and still use `transparent` for unspecified ones.", )), } } juniper_codegen-0.16.0/src/graphql_scalar/mod.rs000064400000000000000000000777731046102023000177740ustar 00000000000000//! Code generation for [GraphQL scalar][1]. //! //! [1]: https://spec.graphql.org/October2021#sec-Scalars use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, visit_mut::VisitMut, }; use url::Url; use crate::common::{ filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, scalar, AttrNames, Description, SpanContainer, }; pub mod attr; pub mod derive; /// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when /// generating code for [GraphQL scalar][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars #[derive(Debug, Default)] struct Attr { /// Name of this [GraphQL scalar][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars name: Option>, /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars description: Option>, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars specified_by_url: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to /// be generic over any [`ScalarValue`] type too. That's why this type /// should be specified only if one of the variants implements /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: Option>, /// Explicitly specified function to be used as /// [`ToInputValue::to_input_value`] implementation. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value to_output: Option>, /// Explicitly specified function to be used as /// [`FromInputValue::from_input_value`] implementation. /// /// [`FromInputValue::from_input_value`]: juniper::FromInputValue::from_input_value from_input: Option>, /// Explicitly specified resolver to be used as /// [`ParseScalarValue::from_str`] implementation. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str parse_token: Option>, /// Explicitly specified module with all custom resolvers for /// [`Self::to_output`], [`Self::from_input`] and [`Self::parse_token`]. with: Option>, /// Explicit where clause added to [`syn::WhereClause`]. where_clause: Option>>, /// Indicator for single-field structs allowing to delegate implmemntations /// of non-provided resolvers to that field. transparent: bool, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "specified_by_url" => { input.parse::()?; let lit = input.parse::()?; let url = lit.value().parse::().map_err(|err| { syn::Error::new(lit.span(), format!("Invalid URL: {err}")) })?; out.specified_by_url .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "to_output_with" => { input.parse::()?; let scl = input.parse::()?; out.to_output .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "from_input_with" => { input.parse::()?; let scl = input.parse::()?; out.from_input .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "parse_token_with" => { input.parse::()?; let scl = input.parse::()?; out.parse_token .replace(SpanContainer::new( ident.span(), Some(scl.span()), ParseToken::Custom(scl), )) .none_or_else(|_| err::dup_arg(&ident))? } "parse_token" => { let types; let _ = syn::parenthesized!(types in input); let parsed_types = types.parse_terminated(syn::Type::parse, token::Comma)?; if parsed_types.is_empty() { return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); } out.parse_token .replace(SpanContainer::new( ident.span(), Some(parsed_types.span()), ParseToken::Delegated(parsed_types.into_iter().collect()), )) .none_or_else(|_| err::dup_arg(&ident))? } "with" => { input.parse::()?; let scl = input.parse::()?; out.with .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "where" => { let (span, parsed_predicates) = { let predicates; let _ = syn::parenthesized!(predicates in input); let parsed_predicates = predicates .parse_terminated(syn::WherePredicate::parse, token::Comma)?; if parsed_predicates.is_empty() { return Err(syn::Error::new( ident.span(), "expected at least 1 where predicate", )); } ( parsed_predicates.span(), parsed_predicates.into_iter().collect(), ) }; out.where_clause .replace(SpanContainer::new( ident.span(), Some(span), parsed_predicates, )) .none_or_else(|_| err::dup_arg(&ident))? } "transparent" => { out.transparent = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), scalar: try_merge_opt!(scalar: self, another), to_output: try_merge_opt!(to_output: self, another), from_input: try_merge_opt!(from_input: self, another), parse_token: try_merge_opt!(parse_token: self, another), with: try_merge_opt!(with: self, another), where_clause: try_merge_opt!(where_clause: self, another), transparent: self.transparent || another.transparent, }) } /// Parses an [`Attr`] from the provided multiple [`syn::Attribute`]s with /// the specified `names`, placed on a type definition. fn from_attrs(names: impl AttrNames, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(names, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if attr.description.is_none() { attr.description = Description::parse_from_doc_attrs(attrs)?; } Ok(attr) } } /// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of /// `#[derive(GraphQLScalar)]`. #[derive(Clone)] enum TypeOrIdent { /// [`syn::Type`]. Type(Box), /// [`syn::Ident`]. Ident(syn::Ident), } /// Definition of [GraphQL scalar][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars struct Definition { /// Name of this [GraphQL scalar][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars name: String, /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars ty: TypeOrIdent, /// Additional [`Self::generics`] [`syn::WhereClause`] predicates. where_clause: Vec, /// Generics of the Rust type that this [GraphQL scalar][1] is implemented /// for. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars generics: syn::Generics, /// [`GraphQLScalarMethods`] representing [GraphQL scalar][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars methods: Methods, /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars description: Option, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars specified_by_url: Option, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL scalar][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: scalar::Type, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_output_and_input_type_tokens().to_tokens(into); self.impl_type_tokens().to_tokens(into); self.impl_value_tokens().to_tokens(into); self.impl_value_async_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing [`marker::IsInputType`] and /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. /// /// [`marker::IsInputType`]: juniper::marker::IsInputType /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/October2021#sec-Scalars #[must_use] fn impl_output_and_input_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::marker::IsInputType<#scalar> for #ty #where_clause { } #[automatically_derived] impl #impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { } } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL scalar][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let description = &self.description; let specified_by_url = self.specified_by_url.as_ref().map(|url| { let url_lit = url.as_str(); quote! { .specified_by_url(#url_lit) } }); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name( _: &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar>, ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { registry.build_scalar_type::(info) #description #specified_by_url .into_meta() } } } } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL scalar][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let resolve = self.methods.expand_resolve(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = (); type TypeInfo = (); fn type_name<'i>( &self, info: &'i Self::TypeInfo, ) -> ::core::option::Option<&'i ::core::primitive::str> { >::name(info) } fn resolve( &self, info: &(), selection: ::core::option::Option<&[::juniper::Selection<'_, #scalar>]>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #resolve } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL scalar][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (ty, generics) = self.impl_self_and_generics(true); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_async<'b>( &'b self, info: &'b Self::TypeInfo, selection_set: ::core::option::Option<&'b [::juniper::Selection<'_, #scalar>]>, executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); ::std::boxed::Box::pin(::juniper::futures::future::ready(v)) } } } } /// Returns generated code implementing [`InputValue`] trait for this /// [GraphQL scalar][1]. /// /// [`InputValue`]: juniper::InputValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_to_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let to_input_value = self.methods.expand_to_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::ToInputValue<#scalar> for #ty #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { #to_input_value } } } } /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL scalar][1]. /// /// [`FromInputValue`]: juniper::FromInputValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_from_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let from_input_value = self.methods.expand_from_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { type Error = ::juniper::executor::FieldError<#scalar>; fn from_input_value( input: &::juniper::InputValue<#scalar>, ) -> ::core::result::Result { #from_input_value .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error) } } } } /// Returns generated code implementing [`ParseScalarValue`] trait for this /// [GraphQL scalar][1]. /// /// [`ParseScalarValue`]: juniper::ParseScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let from_str = self.methods.expand_parse_scalar_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::ParseScalarValue<#scalar> for #ty #where_clause { fn from_str( token: ::juniper::parser::ScalarToken<'_>, ) -> ::juniper::ParseScalarResult<#scalar> { #from_str } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL scalar][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] impl #impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] impl #impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[>::NAME]; } #[automatically_derived] impl #impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } /// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`] /// trait (and similar) implementation. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType #[must_use] fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) { let mut generics = self.generics.clone(); let ty = match &self.ty { TypeOrIdent::Type(ty) => ty.into_token_stream(), TypeOrIdent::Ident(ident) => { let (_, ty_gen, _) = self.generics.split_for_impl(); quote! { #ident #ty_gen } } }; if !self.where_clause.is_empty() { generics .make_where_clause() .predicates .extend(self.where_clause.clone()) } let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { let mut generics = self.generics.clone(); ModifyLifetimes.visit_generics_mut(&mut generics); let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ty = match self.ty.clone() { TypeOrIdent::Type(mut ty) => { ModifyLifetimes.visit_type_mut(&mut ty); ty.into_token_stream() } TypeOrIdent::Ident(ident) => { let (_, ty_gens, _) = generics.split_for_impl(); quote! { #ident #ty_gens } } }; quote! { for<#( #lifetimes ),*> #ty } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } (ty, generics) } } /// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a /// lifetime name that is already in scope" error. struct ModifyLifetimes; impl VisitMut for ModifyLifetimes { fn visit_lifetime_mut(&mut self, lf: &mut syn::Lifetime) { lf.ident = format_ident!("__fa__{}", lf.ident.unraw()); } } /// Methods representing [GraphQL scalar][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars enum Methods { /// [GraphQL scalar][1] represented with only custom resolvers. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars Custom { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: syn::ExprPath, /// Function provided with `#[graphql(from_input_with = ...)]`. from_input: syn::ExprPath, /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` /// or `#[graphql(parse_token(...))]`. parse_token: ParseToken, }, /// [GraphQL scalar][1] maybe partially represented with custom resolver. /// Other methods are used from [`Field`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars Delegated { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: Option, /// Function provided with `#[graphql(from_input_with = ...)]`. from_input: Option, /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` /// or `#[graphql(parse_token(...))]`. parse_token: Option, /// [`Field`] to resolve not provided methods. field: Box, }, } impl Methods { /// Expands [`GraphQLValue::resolve`] method. /// /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } | Self::Delegated { to_output: Some(to_output), .. } => { quote! { ::core::result::Result::Ok(#to_output(self)) } } Self::Delegated { field, .. } => { quote! { ::juniper::GraphQLValue::<#scalar>::resolve( &self.#field, info, selection, executor, ) } } } } /// Expands [`ToInputValue::to_input_value`] method. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } | Self::Delegated { to_output: Some(to_output), .. } => { quote! { let v = #to_output(self); ::juniper::ToInputValue::to_input_value(&v) } } Self::Delegated { field, .. } => { quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } } } } /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { from_input, .. } | Self::Delegated { from_input: Some(from_input), .. } => { quote! { #from_input(input) } } Self::Delegated { field, .. } => { let field_ty = field.ty(); let self_constructor = field.closure_constructor(); quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) .map(#self_constructor) } } } } /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str fn expand_parse_scalar_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { parse_token, .. } | Self::Delegated { parse_token: Some(parse_token), .. } => { let parse_token = parse_token.expand_from_str(scalar); quote! { #parse_token } } Self::Delegated { field, .. } => { let field_ty = field.ty(); quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } } } } } /// Representation of [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str #[derive(Clone, Debug)] enum ParseToken { /// Custom method. Custom(syn::ExprPath), /// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until /// first success. /// /// [`ParseScalarValue`]: juniper::ParseScalarValue Delegated(Vec), } impl ParseToken { /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom(parse_token) => { quote! { #parse_token(token) } } Self::Delegated(delegated) => delegated .iter() .fold(None, |acc, ty| { acc.map_or_else( || Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }), |prev| { Some(quote! { #prev.or_else(|_| { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }) }) } ) }) .unwrap_or_default(), } } } /// Struct field to resolve not provided methods. enum Field { /// Named [`Field`]. Named(syn::Field), /// Unnamed [`Field`]. Unnamed(syn::Field), } impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Named(f) => f.ident.to_tokens(tokens), Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), } } } impl Field { /// [`syn::Type`] of this [`Field`]. fn ty(&self) -> &syn::Type { match self { Self::Named(f) | Self::Unnamed(f) => &f.ty, } } /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn closure_constructor(&self) -> TokenStream { match self { Field::Named(syn::Field { ident, .. }) => { quote! { |v| Self { #ident: v } } } Field::Unnamed(_) => quote! { Self }, } } } juniper_codegen-0.16.0/src/graphql_subscription/attr.rs000064400000000000000000000017351046102023000214270ustar 00000000000000//! Code generation for `#[graphql_subscription]` macro. use proc_macro2::{Span, TokenStream}; use crate::{ common::parse, graphql_object::{attr::expand_on_impl, Attr}, }; use super::Subscription; /// Expands `#[graphql_subscription]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body) { if ast.trait_.is_none() { let impl_attrs = parse::attr::unite(("graphql_subscription", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_subscription", "graphql"], ast.attrs); return expand_on_impl::( Attr::from_attrs(["graphql_subscription", "graphql"], &impl_attrs)?, ast, ); } } Err(syn::Error::new( Span::call_site(), "#[graphql_subscription] attribute is applicable to non-trait `impl` blocks only", )) } juniper_codegen-0.16.0/src/graphql_subscription/mod.rs000064400000000000000000000122661046102023000212350ustar 00000000000000//! Code generation for [GraphQL subscription][1]. //! //! [1]: https://spec.graphql.org/October2021#sec-Subscription pub mod attr; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse_quote; use crate::{common::field, graphql_object::Definition}; /// [GraphQL subscription operation][2] of the [`Definition`] to generate code /// for. /// /// [2]: https://spec.graphql.org/October2021#sec-Subscription struct Subscription; impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_subscription_value_tokens() .to_tokens(into); } } impl Definition { /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL subscription][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } fn resolve_field( &self, _: &Self::TypeInfo, _: &::core::primitive::str, _: &::juniper::Arguments<'_, #scalar>, _: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { ::core::result::Result::Err(::juniper::FieldError::from( "Called `resolve_field` on subscription object", )) } fn concrete_type_name( &self, _: &Self::Context, _: &Self::TypeInfo, ) -> ::std::string::String { #name.into() } } } } /// Returns generated code implementing [`GraphQLSubscriptionValue`] trait /// for this [GraphQL subscription][1]. /// /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue /// [1]: https://spec.graphql.org/October2021#sec-Subscription #[must_use] fn impl_graphql_subscription_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; // We use `for_async = false` here as `GraphQLSubscriptionValue` requires // simpler and less `Send`/`Sync` bounds than `GraphQLValueAsync`. let (impl_generics, mut where_clause) = self.impl_generics(false); if scalar.is_generic() { where_clause = Some(where_clause.unwrap_or_else(|| parse_quote! { where })); where_clause .as_mut() .unwrap() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); let fields_resolvers = self .fields .iter() .map(|f| f.method_resolve_field_into_stream_tokens(scalar)); let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); quote! { #[allow(deprecated)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #where_clause { fn resolve_field_into_stream< 's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f, >( &'s self, info: &'i Self::TypeInfo, field: &'fi ::core::primitive::str, args: ::juniper::Arguments<'args, #scalar>, executor: &'ref_e ::juniper::Executor<'ref_e, 'e, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'f, std::result::Result< ::juniper::Value<::juniper::ValuesStream<'res, #scalar>>, ::juniper::FieldError<#scalar>, >> where 's: 'f, 'fi: 'f, 'args: 'f, 'ref_e: 'f, 'res: 'f, 'i: 'res, 'e: 'res, { match field { #( #fields_resolvers )* _ => ::std::boxed::Box::pin(async move { #no_field_err }), } } } } } } juniper_codegen-0.16.0/src/graphql_union/attr.rs000064400000000000000000000151771046102023000200400ustar 00000000000000//! Code generation for `#[graphql_union]` macro. use std::mem; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::common::{diagnostic, parse, path_eq_single, scalar, SpanContainer}; use super::{ all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, VariantDefinition, }; /// [`diagnostic::Scope`] of errors for `#[graphql_union]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::UnionAttr; /// Expands `#[graphql_union]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body) { let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip(["graphql_union", "graphql"], ast.attrs); return expand_on_trait(trait_attrs, ast); } Err(syn::Error::new( Span::call_site(), "#[graphql_union] attribute is applicable to trait definitions only", )) } /// Expands `#[graphql_union]` macro placed on a trait definition. fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { let attr = Attr::from_attrs(["graphql_union", "graphql"], &attrs)?; let trait_span = ast.span(); let trait_ident = &ast.ident; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| trait_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| trait_ident.span()), ); } let mut variants: Vec<_> = ast .items .iter_mut() .filter_map(|i| match i { syn::TraitItem::Fn(m) => parse_variant_from_trait_method(m, trait_ident, &attr), _ => None, }) .collect(); diagnostic::abort_if_dirty(); emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(trait_span, "expects at least one union variant"); } if !all_variants_different(&variants) { ERR.emit_custom( trait_span, "must have a different type for each union variant", ); } diagnostic::abort_if_dirty(); let context = attr .context .map(SpanContainer::into_inner) .or_else(|| variants.iter().find_map(|v| v.context.as_ref()).cloned()) .unwrap_or_else(|| parse_quote! { () }); let generated_code = Definition { name, ty: parse_quote! { #trait_ident }, is_trait_object: true, description: attr.description.map(SpanContainer::into_inner), context, scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics.clone(), variants, }; Ok(quote! { #ast #generated_code }) } /// Parses given Rust trait `method` as [GraphQL union][1] variant. /// /// On failure returns [`None`] and internally fills up [`diagnostic`] /// with the corresponding errors. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions fn parse_variant_from_trait_method( method: &mut syn::TraitItemFn, trait_ident: &syn::Ident, trait_attr: &Attr, ) -> Option { let method_attrs = method.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. method.attrs = mem::take(&mut method.attrs) .into_iter() .filter(|attr| !path_eq_single(attr.path(), "graphql")) .collect(); let attr = VariantAttr::from_attrs("graphql", &method_attrs) .map_err(diagnostic::emit_error) .ok()?; if let Some(rslvr) = attr.external_resolver { ERR.custom( rslvr.span_ident(), "cannot use #[graphql(with = ...)] attribute on a trait method", ) .note(String::from( "instead use #[graphql(ignore)] on the method with \ #[graphql_union(on ... = ...)] on the trait itself", )) .emit() } if attr.ignore.is_some() { return None; } let method_span = method.sig.span(); let method_ident = &method.sig.ident; let ty = parse::downcaster::output_type(&method.sig.output) .map_err(|span| { ERR.emit_custom( span, "expects trait method return type to be `Option<&VariantType>` only", ) }) .ok()?; let method_context_ty = parse::downcaster::context_ty(&method.sig) .map_err(|span| { ERR.emit_custom( span, "expects trait method to accept `&self` only and, optionally, `&Context`", ) }) .ok()?; if let Some(is_async) = &method.sig.asyncness { ERR.emit_custom( is_async.span(), "async downcast to union variants is not supported", ); return None; } let resolver_code = { if let Some(other) = trait_attr.external_resolvers.get(&ty) { ERR.custom( method_span, format!( "trait method `{method_ident}` conflicts with the external \ resolver function `{}` declared on the trait to resolve \ the variant type `{}`", other.to_token_stream(), ty.to_token_stream(), ), ) .note(String::from( "use `#[graphql(ignore)]` attribute to ignore this trait \ method for union variants resolution", )) .emit(); } if method_context_ty.is_some() { parse_quote! { #trait_ident::#method_ident(self, ::juniper::FromContext::from(context)) } } else { parse_quote! { #trait_ident::#method_ident(self) } } }; // Doing this may be quite an expensive, because resolving may contain some // heavy computation, so we're preforming it twice. Unfortunately, we have // no other options here, until the `juniper::GraphQLType` itself will allow // to do it in some cleverer way. let resolver_check = parse_quote! { ({ #resolver_code } as ::core::option::Option<&#ty>).is_some() }; Some(VariantDefinition { ty, resolver_code, resolver_check, context: method_context_ty, }) } juniper_codegen-0.16.0/src/graphql_union/derive.rs000064400000000000000000000151761046102023000203430ustar 00000000000000//! Code generation for `#[derive(GraphQLUnion)]` macro. use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; use crate::common::{ diagnostic::{self, ResultExt as _}, parse::TypeExt as _, scalar, SpanContainer, }; use super::{ all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, VariantDefinition, }; /// [`diagnostic::Scope`] of errors for `#[derive(GraphQLUnion)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::UnionDerive; /// Expands `#[derive(GraphQLUnion)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input).unwrap_or_abort(); match &ast.data { Data::Enum(_) => expand_enum(ast), Data::Struct(_) => expand_struct(ast), _ => Err(ERR.custom_error(ast.span(), "can only be derived for enums and structs")), } .map(ToTokens::into_token_stream) } /// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a /// Rust enum. fn expand_enum(ast: syn::DeriveInput) -> syn::Result { let attr = Attr::from_attrs("graphql", &ast.attrs)?; let enum_span = ast.span(); let enum_ident = ast.ident; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| enum_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| enum_ident.span()), ); } let mut variants: Vec<_> = match ast.data { Data::Enum(data) => data.variants, data => unreachable!("graphql_union::derive::expand_enum({data:?})"), } .into_iter() .filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &attr)) .collect(); diagnostic::abort_if_dirty(); emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(enum_span, "expects at least one union variant"); } if !all_variants_different(&variants) { ERR.emit_custom( enum_span, "must have a different type for each union variant", ); } diagnostic::abort_if_dirty(); Ok(Definition { name, ty: parse_quote! { #enum_ident }, is_trait_object: false, description: attr.description.map(SpanContainer::into_inner), context: attr .context .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, }) } /// Parses given Rust enum `var`iant as [GraphQL union][1] variant. /// /// On failure returns [`None`] and internally fills up [`diagnostic`] /// with the corresponding errors. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions fn parse_variant_from_enum_variant( var: syn::Variant, enum_ident: &syn::Ident, enum_attr: &Attr, ) -> Option { let attr = VariantAttr::from_attrs("graphql", &var.attrs) .map_err(diagnostic::emit_error) .ok()?; if attr.ignore.is_some() { return None; } let var_ident = var.ident; let ty = match var.fields { Fields::Unnamed(fields) => { let mut iter = fields.unnamed.iter(); let first = iter.next().unwrap(); if iter.next().is_none() { Ok(first.ty.unparenthesized().clone()) } else { Err(fields.span()) } } _ => Err(var_ident.span()), } .map_err(|span| { ERR.emit_custom( span, "enum allows only unnamed variants with a single field, e.g. `Some(T)`", ) }) .ok()?; let enum_path = quote! { #enum_ident::#var_ident }; let resolver_code = if let Some(rslvr) = attr.external_resolver { if let Some(other) = enum_attr.external_resolvers.get(&ty) { ERR.emit_custom( rslvr.span_ident(), format!( "variant `{}` already has external resolver function `{}` \ declared on the enum", ty.to_token_stream(), other.to_token_stream(), ), ); } let resolver_fn = rslvr.into_inner(); parse_quote! { #resolver_fn(self, ::juniper::FromContext::from(context)) } } else { parse_quote! { match self { #enum_ident::#var_ident(ref v) => ::core::option::Option::Some(v), _ => ::core::option::Option::None, } } }; let resolver_check = parse_quote! { ::core::matches!(self, #enum_path(_)) }; Some(VariantDefinition { ty, resolver_code, resolver_check, context: None, }) } /// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a /// Rust struct. fn expand_struct(ast: syn::DeriveInput) -> syn::Result { let attr = Attr::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); let struct_ident = ast.ident; let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), ); } let mut variants = vec![]; emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(struct_span, "expects at least one union variant"); } if !all_variants_different(&variants) { ERR.emit_custom( struct_span, "must have a different type for each union variant", ); } diagnostic::abort_if_dirty(); Ok(Definition { name, ty: parse_quote! { #struct_ident }, is_trait_object: false, description: attr.description.map(SpanContainer::into_inner), context: attr .context .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, }) } juniper_codegen-0.16.0/src/graphql_union/mod.rs000064400000000000000000000742101046102023000176360ustar 00000000000000//! Code generation for [GraphQL union][1]. //! //! [1]: https://spec.graphql.org/October2021#sec-Unions pub mod attr; pub mod derive; use std::collections::HashMap; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, }; use crate::common::{ filter_attrs, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, scalar, AttrNames, Description, SpanContainer, }; /// Helper alias for the type of [`Attr::external_resolvers`] field. type AttrResolvers = HashMap>; /// Available arguments behind `#[graphql]` (or `#[graphql_union]`) attribute /// when generating code for [GraphQL union][1] type. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct Attr { /// Explicitly specified name of [GraphQL union][1] type. /// /// If [`None`], then Rust type name is used by default. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions name: Option>, /// Explicitly specified [description][2] of [GraphQL union][1] type. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL union][1] type with. /// /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Unions context: Option>, /// Explicitly specified type of [`ScalarValue`] to use for resolving this /// [GraphQL union][1] type with. /// /// If [`None`], then generated code will be generic over any /// [`ScalarValue`] type, which, in turn, requires all [union][1] variants /// to be generic over any [`ScalarValue`] type too. That's why this type /// should be specified only if one of the variants implements /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: Option>, /// Explicitly specified external resolver functions for [GraphQL union][1] /// variants. /// /// If [`None`], then macro will try to auto-infer all the possible variants /// from the type declaration, if possible. That's why specifying an /// external resolver function has sense, when some custom [union][1] /// variant resolving logic is involved, or variants cannot be inferred. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions external_resolvers: AttrResolvers, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. is_internal: bool, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), name.value(), )) .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; out.description .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; out.context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; let scl = input.parse::()?; out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "on" => { let ty = input.parse::()?; input.parse::()?; let rslvr = input.parse::()?; let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr); let rslvr_span = rslvr_spanned.span_joined(); out.external_resolvers .insert(ty, rslvr_spanned) .none_or_else(|_| err::dup_arg(rslvr_span))? } "internal" => { out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), external_resolvers: try_merge_hashmap!( external_resolvers: self, another => span_joined ), is_internal: self.is_internal || another.is_internal, }) } /// Parses an [`Attr`] from the provided multiple [`syn::Attribute`]s with /// the specified `names`, placed on a trait or type definition. fn from_attrs(names: impl AttrNames, attrs: &[syn::Attribute]) -> syn::Result { let mut meta = filter_attrs(names, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if meta.description.is_none() { meta.description = Description::parse_from_doc_attrs(attrs)?; } Ok(meta) } } /// Available arguments behind `#[graphql]` attribute when generating code for /// [GraphQL union][1]'s variant. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct VariantAttr { /// Explicitly specified marker for the variant/field being ignored and not /// included into [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions ignore: Option>, /// Explicitly specified external resolver function for this [GraphQL union][1] variant. /// /// If absent, then macro will generate the code which just returns the variant inner value. /// Usually, specifying an external resolver function has sense, when some custom resolving /// logic is involved. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions external_resolver: Option>, } impl Parse for VariantAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, "with" => { input.parse::()?; let rslvr = input.parse::()?; out.external_resolver .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr)) .none_or_else(|_| err::dup_arg(&ident))? } name => { return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; } Ok(out) } } impl VariantAttr { /// Tries to merge two [`VariantAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { ignore: try_merge_opt!(ignore: self, another), external_resolver: try_merge_opt!(external_resolver: self, another), }) } /// Parses [`VariantAttr`] from the given multiple `name`d /// [`syn::Attribute`]s placed on a variant/field/method definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) } } /// Definition of [GraphQL union][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions struct Definition { /// Name of this [GraphQL union][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions name: String, /// Rust type that this [GraphQL union][1] is represented with. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions ty: syn::Type, /// Generics of the Rust type that this [GraphQL union][1] is implemented /// for. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions generics: syn::Generics, /// Indicator whether code should be generated for a trait object, rather /// than for a regular Rust type. is_trait_object: bool, /// Description of this [GraphQL union][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL union][1]. /// /// [`Context`]: juniper::Context /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Unions context: syn::Type, /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation /// with for this [GraphQL union][1]. /// /// If [`None`] then generated code will be generic over any [`ScalarValue`] /// type, which, in turn, requires all [union][1] variants to be generic /// over any [`ScalarValue`] type too. That's why this type should be /// specified only if one of the variants implements [`GraphQLType`] in a /// non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: scalar::Type, /// Variants definitions of this [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions variants: Vec, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_graphql_union_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } impl Definition { /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`] /// trait (and similar) implementation of this [GraphQL union][1]. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_generics( &self, for_async: bool, ) -> (TokenStream, TokenStream, Option) { let (_, ty_generics, _) = self.generics.split_for_impl(); let ty = &self.ty; let mut ty_full = quote! { #ty #ty_generics }; if self.is_trait_object { ty_full = quote! { dyn #ty_full + '__obj + ::core::marker::Send + ::core::marker::Sync }; } let mut generics = self.generics.clone(); if self.is_trait_object { generics.params.push(parse_quote! { '__obj }); } let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } if let Some(bound) = scalar.bounds() { generics.make_where_clause().predicates.push(bound); } if for_async { let self_ty = if !self.is_trait_object && self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{ident}"); } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); let ty = &self.ty; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty #ty_generics } } else { quote! { Self } }; generics .make_where_clause() .predicates .push(parse_quote! { #self_ty: ::core::marker::Sync }); if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::core::marker::Send + ::core::marker::Sync }); } } let (impl_generics, _, where_clause) = generics.split_for_impl(); ( quote! { #impl_generics }, quote! { #ty_full }, where_clause.cloned(), ) } /// Returns generated code implementing [`GraphQLUnion`] trait for this /// [GraphQL union][1]. /// /// [`GraphQLUnion`]: juniper::GraphQLUnion /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_union_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_full, where_clause) = self.impl_generics(false); let variant_tys: Vec<_> = self.variants.iter().map(|var| &var.ty).collect(); let all_variants_unique = (variant_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #variant_tys ),*); } }); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause { fn mark() { #all_variants_unique #( <#variant_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } } } /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL union][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_full, where_clause) = self.impl_generics(false); let variant_tys = self.variants.iter().map(|var| &var.ty); quote! { #[automatically_derived] impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause { fn mark() { #( <#variant_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } } } /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL union][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_full, where_clause) = self.impl_generics(false); let name = &self.name; let description = &self.description; let variant_tys = self.variants.iter().map(|var| &var.ty); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause { fn name( _ : &Self::TypeInfo, ) -> ::core::option::Option<&'static ::core::primitive::str> { ::core::option::Option::Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut ::juniper::Registry<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { let types = [ #( registry.get_type::<#variant_tys>(info), )* ]; registry.build_union_type::<#ty_full>(info, &types) #description .into_meta() } } } } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL union][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; let context = &self.context; let (impl_generics, ty_full, where_clause) = self.impl_generics(false); let name = &self.name; let match_variant_names = self .variants .iter() .map(|v| v.method_concrete_type_name_tokens(scalar)); let variant_resolvers = self .variants .iter() .map(|v| v.method_resolve_into_type_tokens(scalar)); quote! { #[automatically_derived] impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause { type Context = #context; type TypeInfo = (); fn type_name<'__i>( &self, info: &'__i Self::TypeInfo, ) -> ::core::option::Option<&'__i ::core::primitive::str> { >::name(info) } fn concrete_type_name( &self, context: &Self::Context, info: &Self::TypeInfo, ) -> ::std::string::String { #( #match_variant_names )* ::core::panic!( "GraphQL union `{}` cannot be resolved into any of its \ variants in its current state", #name, ); } fn resolve_into_type( &self, info: &Self::TypeInfo, type_name: &::core::primitive::str, _: ::core::option::Option<&[::juniper::Selection<'_, #scalar>]>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { let context = executor.context(); #( #variant_resolvers )* return ::core::result::Result::Err(::juniper::FieldError::from(::std::format!( "Concrete type `{}` is not handled by instance \ resolvers on GraphQL union `{}`", type_name, #name, ))); } } } } /// Returns generated code implementing [`GraphQLValueAsync`] trait for this /// [GraphQL union][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_full, where_clause) = self.impl_generics(true); let name = &self.name; let variant_async_resolvers = self .variants .iter() .map(|v| v.method_resolve_into_type_async_tokens(scalar)); quote! { #[allow(non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause { fn resolve_into_type_async<'b>( &'b self, info: &'b Self::TypeInfo, type_name: &::core::primitive::str, _: ::core::option::Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let context = executor.context(); #( #variant_async_resolvers )* return ::juniper::macros::helper::err_fut(::std::format!( "Concrete type `{}` is not handled by instance \ resolvers on GraphQL union `{}`", type_name, #name, )); } } } } /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL union][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/October2021#sec-Unions #[must_use] pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let variants = self.variants.iter().map(|var| &var.ty); let (impl_generics, ty, where_clause) = self.impl_generics(false); quote! { #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, #(<#variants as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } } /// Definition of [GraphQL union][1] variant for code generation. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions struct VariantDefinition { /// Rust type that this [GraphQL union][1] variant resolves into. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions ty: syn::Type, /// Rust code for value resolution of this [GraphQL union][1] variant. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_code: syn::Expr, /// Rust code for checking whether [GraphQL union][1] should be resolved /// into this variant. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_check: syn::Expr, /// Rust type of [`Context`] that this [GraphQL union][1] variant requires /// for resolution. /// /// It's available only when code generation happens for Rust traits and a /// trait method contains context argument. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/October2021#sec-Unions context: Option, } impl VariantDefinition { /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] /// method, which returns name of the underlying GraphQL type contained in /// this [`VariantDefinition`]. /// /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] fn method_concrete_type_name_tokens(&self, scalar: &scalar::Type) -> TokenStream { let ty = &self.ty; let check = &self.resolver_check; quote! { if #check { return <#ty as ::juniper::GraphQLType<#scalar>>::name(info) .unwrap() .to_string(); } } } /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] /// method, which resolves the underlying GraphQL type contained in this /// [`VariantDefinition`] synchronously. /// /// [0]: juniper::GraphQLValue::resolve_into_type #[must_use] fn method_resolve_into_type_tokens(&self, scalar: &scalar::Type) -> TokenStream { let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); let expr = &self.resolver_code; let resolving_code = gen::sync_resolving_code(); quote! { if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info) .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))? { let res = { #expr }; return #resolving_code; } } } /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which /// resolves the underlying GraphQL type contained in this /// [`VariantDefinition`] asynchronously. /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async #[must_use] fn method_resolve_into_type_async_tokens(&self, scalar: &scalar::Type) -> TokenStream { let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); let expr = &self.resolver_code; let resolving_code = gen::async_resolving_code(None); quote! { match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) { ::core::option::Option::Some(name) => { if type_name == name { let fut = ::juniper::futures::future::ready({ #expr }); return #resolving_code; } } ::core::option::Option::None => { return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name); } } } } } /// Emerges [`Attr::external_resolvers`] into the given [GraphQL union][1] /// `variants`. /// /// If duplication happens, then resolving code is overwritten with the one from /// `external_resolvers`. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions fn emerge_union_variants_from_attr( variants: &mut Vec, external_resolvers: AttrResolvers, ) { if external_resolvers.is_empty() { return; } for (ty, rslvr) in external_resolvers { let resolver_fn = rslvr.into_inner(); let resolver_code = parse_quote! { #resolver_fn(self, ::juniper::FromContext::from(context)) }; // Doing this may be quite an expensive, because resolving may contain // some heavy computation, so we're preforming it twice. Unfortunately, // we have no other options here, until the `juniper::GraphQLType` // itself will allow to do it in some cleverer way. let resolver_check = parse_quote! { ({ #resolver_code } as ::core::option::Option<&#ty>).is_some() }; if let Some(var) = variants.iter_mut().find(|v| v.ty == ty) { var.resolver_code = resolver_code; var.resolver_check = resolver_check; } else { variants.push(VariantDefinition { ty, resolver_code, resolver_check, context: None, }) } } } /// Checks whether all [GraphQL union][1] `variants` represent a different Rust /// type. /// /// # Notice /// /// This is not an optimal implementation, as it's possible to bypass this check /// by using a full qualified path instead (`crate::Test` vs `Test`). Since this /// requirement is mandatory, the static assertion [`assert_type_ne_all!`][2] is /// used to enforce this requirement in the generated code. However, due to the /// bad error message this implementation should stay and provide guidance. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: juniper::sa::assert_type_ne_all fn all_variants_different(variants: &[VariantDefinition]) -> bool { let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect(); types.dedup(); types.len() == variants.len() } juniper_codegen-0.16.0/src/lib.rs000064400000000000000000002365411046102023000147660ustar 00000000000000#![doc = include_str!("../README.md")] #![recursion_limit = "1024"] // NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust // doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, // and so we cannot move the definition into a sub-module and use the `#[macro_export]` // attribute. /// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of /// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related /// to the `$another` struct (a later one). /// /// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. /// By default, [`SpanContainer::span_ident`] is used. /// /// [`Span`]: proc_macro2::Span /// [`SpanContainer`]: crate::common::SpanContainer /// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_opt { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if let Some(v) = $self.$field { $another .$field .replace(v) .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } $another.$field }}; ($field:ident: $self:ident, $another:ident) => { try_merge_opt!($field: $self, $another => span_ident) }; } // NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust // doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, // and so we cannot move the definition into a sub-module and use the `#[macro_export]` // attribute. /// Attempts to merge a [`HashMap`] `$field` of a `$self` struct with the same `$field` of /// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error /// with a [`Span`] related to the `$another` struct (a later one). /// /// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. /// By default, [`SpanContainer::span_ident`] is used. /// /// [`HashMap`]: std::collections::HashMap /// [`Span`]: proc_macro2::Span /// [`SpanContainer`]: crate::common::SpanContainer /// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_hashmap { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if !$self.$field.is_empty() { for (ty, rslvr) in $self.$field { $another .$field .insert(ty, rslvr) .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } } $another.$field }}; ($field:ident: $self:ident, $another:ident) => { try_merge_hashmap!($field: $self, $another => span_ident) }; } // NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust // doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, // and so we cannot move the definition into a sub-module and use the `#[macro_export]` // attribute. /// Attempts to merge a [`HashSet`] `$field` of a `$self` struct with the same `$field` of /// `$another` struct. If some [`HashSet`] entries are duplicated, then throws a duplication error /// with a [`Span`] related to the `$another` struct (a later one). /// /// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. /// By default, [`SpanContainer::span_ident`] is used. /// /// [`HashSet`]: std::collections::HashSet /// [`Span`]: proc_macro2::Span /// [`SpanContainer`]: crate::common::SpanContainer /// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident macro_rules! try_merge_hashset { ($field:ident: $self:ident, $another:ident => $span:ident) => {{ if !$self.$field.is_empty() { for ty in $self.$field { $another .$field .replace(ty) .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } } $another.$field }}; ($field:ident: $self:ident, $another:ident) => { try_merge_hashset!($field: $self, $another => span_ident) }; } mod common; mod graphql_enum; mod graphql_input_object; mod graphql_interface; mod graphql_object; mod graphql_scalar; mod graphql_subscription; mod graphql_union; mod scalar_value; use proc_macro::TokenStream; use self::common::diagnostic::{self, ResultExt as _}; /// `#[derive(GraphQLInputObject)]` macro for deriving a /// [GraphQL input object][0] implementation for a Rust struct. Each /// non-ignored field type must itself be [GraphQL input object][0] or a /// [GraphQL scalar][2]. /// /// The `#[graphql]` helper attribute is used for configuring the derived /// implementation. Specifying multiple `#[graphql]` attributes on the same /// definition is totally okay. They all will be treated as a single attribute. /// /// ```rust /// use juniper::GraphQLInputObject; /// /// #[derive(GraphQLInputObject)] /// struct Point2D { /// x: f64, /// y: f64, /// } /// ``` /// /// # Custom name and description /// /// The name of a [GraphQL input object][0] or its [fields][1] may be overridden /// with the `name` attribute's argument. By default, a type name or a struct /// field name is used in a `camelCase`. /// /// The description of a [GraphQL input object][0] or its [fields][1] may be /// specified either with the `description`/`desc` attribute's argument, or with /// a regular Rust doc comment. /// /// ```rust /// # use juniper::GraphQLInputObject; /// # /// #[derive(GraphQLInputObject)] /// #[graphql( /// // Rename the type for GraphQL by specifying the name here. /// name = "Point", /// // You may also specify a description here. /// // If present, doc comments will be ignored. /// desc = "A point is the simplest two-dimensional primitive.", /// )] /// struct Point2D { /// /// Abscissa value. /// x: f64, /// /// #[graphql(name = "y", desc = "Ordinate value")] /// y_coord: f64, /// } /// ``` /// /// # Renaming policy /// /// By default, all [GraphQL input object fields][1] are renamed in a /// `camelCase` manner (so a `y_coord` Rust struct field becomes a /// `yCoord` [value][1] in GraphQL schema, and so on). This complies with /// default GraphQL naming conventions as [demonstrated in spec][0]. /// /// However, if you need for some reason another naming convention, it's /// possible to do so by using the `rename_all` attribute's argument. At the /// moment, it supports the following policies only: `SCREAMING_SNAKE_CASE`, /// `camelCase`, `none` (disables any renaming). /// /// ```rust /// # use juniper::GraphQLInputObject; /// # /// #[derive(GraphQLInputObject)] /// #[graphql(rename_all = "none")] // disables renaming /// struct Point2D { /// x: f64, /// y_coord: f64, // will be `y_coord` instead of `yCoord` in GraphQL schema /// } /// ``` /// /// # Ignoring fields /// /// To omit exposing a Rust field in a GraphQL schema, use the `ignore` /// attribute's argument directly on that field. Ignored fields must implement /// [`Default`] or have the `default = ` attribute's argument. /// /// ```rust /// # use juniper::GraphQLInputObject; /// # /// enum System { /// Cartesian, /// } /// /// #[derive(GraphQLInputObject)] /// struct Point2D { /// x: f64, /// y: f64, /// #[graphql(ignore, default = System::Cartesian)] /// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ /// // This attribute is required, as we need to be able to construct /// // a `Point2D` value from the `{ x: 0.0, y: 0.0 }` GraphQL input value, /// // received from client-side. /// system: System, /// // `Default::default()` value is used, if no /// // `#[graphql(default = )]` is specified. /// #[graphql(skip)] /// // ^^^^ alternative naming, up to your preference /// shift: f64, /// } /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects /// [1]: https://spec.graphql.org/October2021#InputFieldsDefinition /// [2]: https://spec.graphql.org/October2021#sec-Scalars #[proc_macro_derive(GraphQLInputObject, attributes(graphql))] pub fn derive_input_object(input: TokenStream) -> TokenStream { diagnostic::entry_point(|| { graphql_input_object::derive::expand(input.into()) .unwrap_or_abort() .into() }) } /// `#[derive(GraphQLEnum)]` macro for deriving a [GraphQL enum][0] /// implementation for Rust enums. /// /// The `#[graphql]` helper attribute is used for configuring the derived /// implementation. Specifying multiple `#[graphql]` attributes on the same /// definition is totally okay. They all will be treated as a single attribute. /// /// ```rust /// use juniper::GraphQLEnum; /// /// #[derive(GraphQLEnum)] /// enum Episode { /// NewHope, /// Empire, /// Jedi, /// } /// ``` /// /// # Custom name, description and deprecation /// /// The name of a [GraphQL enum][0] or its [values][1] may be overridden with /// the `name` attribute's argument. By default, a type name is used or a /// variant name in `SCREAMING_SNAKE_CASE`. /// /// The description of a [GraphQL enum][0] or its [values][1] may be specified /// either with the `description`/`desc` attribute's argument, or with a regular /// Rust doc comment. /// /// [GraphQL enum value][1] may be deprecated by specifying the `deprecated` /// attribute's argument, or with regular a Rust `#[deprecated]` attribute. /// /// ```rust /// # #![allow(deprecated)] /// # /// # use juniper::GraphQLEnum; /// # /// #[derive(GraphQLEnum)] /// #[graphql( /// // Rename the type for GraphQL by specifying the name here. /// name = "AvailableEpisodes", /// // You may also specify a description here. /// // If present, doc comments will be ignored. /// desc = "Possible episodes.", /// )] /// enum Episode { /// /// Doc comment, also acting as description. /// #[deprecated(note = "Don't use it")] /// NewHope, /// /// #[graphql(name = "Jedi", desc = "Arguably the best one in the trilogy")] /// #[graphql(deprecated = "Don't use it")] /// Jedai, /// /// Empire, /// } /// ``` /// /// # Renaming policy /// /// By default, all [GraphQL enum values][1] are renamed in a /// `SCREAMING_SNAKE_CASE` manner (so a `NewHope` Rust enum variant becomes a /// `NEW_HOPE` [value][1] in GraphQL schema, and so on). This complies with /// default GraphQL naming conventions as [demonstrated in spec][0]. /// /// However, if you need for some reason another naming convention, it's /// possible to do so by using the `rename_all` attribute's argument. At the /// moment, it supports the following policies only: `SCREAMING_SNAKE_CASE`, /// `camelCase`, `none` (disables any renaming). /// /// ```rust /// # use juniper::GraphQLEnum; /// # /// #[derive(GraphQLEnum)] /// #[graphql(rename_all = "none")] // disables renaming /// enum Episode { /// NewHope, /// Empire, /// Jedi, /// } /// ``` /// /// # Ignoring enum variants /// /// To omit exposing a Rust enum variant in a GraphQL schema, use the `ignore` /// attribute's argument directly on that variant. Only ignored Rust enum /// variants are allowed to contain fields. /// /// ```rust /// # use juniper::GraphQLEnum; /// # /// #[derive(GraphQLEnum)] /// enum Episode { /// NewHope, /// Empire, /// Jedi, /// #[graphql(ignore)] /// Legends(T), /// #[graphql(skip)] /// // ^^^^ alternative naming, up to your preference /// CloneWars(T), /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, `#[derive(GraphQLEnum)]` macro generates code, which is generic /// over a [`ScalarValue`] type. This can be changed with the `scalar` /// attribute's argument. /// /// ```rust /// # use juniper::{DefaultScalarValue, GraphQLEnum}; /// # /// #[derive(GraphQLEnum)] /// #[graphql(scalar = DefaultScalarValue)] /// enum Episode { /// NewHope, /// Empire, /// Jedi, /// } /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021#sec-Enums /// [1]: https://spec.graphql.org/October2021#sec-Enum-Value #[proc_macro_derive(GraphQLEnum, attributes(graphql))] pub fn derive_enum(input: TokenStream) -> TokenStream { diagnostic::entry_point(|| { graphql_enum::derive::expand(input.into()) .unwrap_or_abort() .into() }) } /// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][0] /// implementation. /// /// # Transparent delegation /// /// Quite often we want to create a custom [GraphQL scalar][0] type by just /// wrapping an existing one, inheriting all its behavior. In Rust, this is /// often called as ["newtype pattern"][1]. This is achieved by annotating /// the definition with the `#[graphql(transparent)]` attribute: /// ```rust /// # use juniper::{GraphQLObject, GraphQLScalar}; /// # /// #[derive(GraphQLScalar)] /// #[graphql(transparent)] /// struct UserId(String); /// /// #[derive(GraphQLScalar)] /// #[graphql(transparent)] /// struct DroidId { /// value: String, /// } /// /// #[derive(GraphQLObject)] /// struct Pair { /// user_id: UserId, /// droid_id: DroidId, /// } /// ``` /// /// The inherited behaviour may also be customized: /// ```rust /// # use juniper::GraphQLScalar; /// # /// /// Doc comments are used for the GraphQL type description. /// #[derive(GraphQLScalar)] /// #[graphql( /// // Custom GraphQL name. /// name = "MyUserId", /// // Description can also specified in the attribute. /// // This will the doc comment, if one exists. /// description = "...", /// // Optional specification URL. /// specified_by_url = "https://tools.ietf.org/html/rfc4122", /// // Explicit generic scalar. /// scalar = S: juniper::ScalarValue, /// transparent, /// )] /// struct UserId(String); /// ``` /// /// All of the methods inherited from `Newtype`'s field may also be overridden /// with the attributes described below. /// /// # Custom resolving /// /// Customization of a [GraphQL scalar][0] type resolving is possible via /// `#[graphql(to_output_with = )]` attribute: /// ```rust /// # use juniper::{GraphQLScalar, ScalarValue, Value}; /// # /// #[derive(GraphQLScalar)] /// #[graphql(to_output_with = to_output, transparent)] /// struct Incremented(i32); /// /// /// Increments [`Incremented`] before converting into a [`Value`]. /// fn to_output(v: &Incremented) -> Value { /// let inc = v.0 + 1; /// Value::from(inc) /// } /// ``` /// /// # Custom parsing /// /// Customization of a [GraphQL scalar][0] type parsing is possible via /// `#[graphql(from_input_with = )]` attribute: /// ```rust /// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; /// # /// #[derive(GraphQLScalar)] /// #[graphql(from_input_with = Self::from_input, transparent)] /// struct UserId(String); /// /// impl UserId { /// /// Checks whether [`InputValue`] is `String` beginning with `id: ` and /// /// strips it. /// fn from_input( /// input: &InputValue, /// ) -> Result { /// // ^^^^^^ must implement `IntoFieldError` /// input.as_string_value() /// .ok_or_else(|| format!("Expected `String`, found: {input}")) /// .and_then(|str| { /// str.strip_prefix("id: ") /// .ok_or_else(|| { /// format!( /// "Expected `UserId` to begin with `id: `, \ /// found: {input}", /// ) /// }) /// }) /// .map(|id| Self(id.into())) /// } /// } /// ``` /// /// # Custom token parsing /// /// Customization of which tokens a [GraphQL scalar][0] type should be parsed is /// possible via `#[graphql(parse_token_with = )]` or /// `#[graphql(parse_token()]` attributes: /// ```rust /// # use juniper::{ /// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, /// # ScalarValue, ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql( /// to_output_with = to_output, /// from_input_with = from_input, /// parse_token_with = parse_token, /// )] /// // ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)`, which /// // tries to parse as `String` first, and then as `i32` if /// // prior fails. /// enum StringOrInt { /// String(String), /// Int(i32), /// } /// /// fn to_output(v: &StringOrInt) -> Value { /// match v { /// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// /// fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// /// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { /// >::from_str(value) /// .or_else(|_| >::from_str(value)) /// } /// ``` /// > __NOTE:__ Once we provide all 3 custom functions, there is no sense in /// > following the [newtype pattern][1] anymore. /// /// # Full behavior /// /// Instead of providing all custom functions separately, it's possible to /// provide a module holding the appropriate `to_output()`, `from_input()` and /// `parse_token()` functions: /// ```rust /// # use juniper::{ /// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, /// # ScalarValue, ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql(with = string_or_int)] /// enum StringOrInt { /// String(String), /// Int(i32), /// } /// /// mod string_or_int { /// use super::*; /// /// pub(super) fn to_output(v: &StringOrInt) -> Value { /// match v { /// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// /// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// /// pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { /// >::from_str(t) /// .or_else(|_| >::from_str(t)) /// } /// } /// # /// # fn main() {} /// ``` /// /// A regular `impl` block is also suitable for that: /// ```rust /// # use juniper::{ /// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, /// # ScalarValue, ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] /// // #[graphql(with = Self)] <- default behaviour, so can be omitted /// enum StringOrInt { /// String(String), /// Int(i32), /// } /// /// impl StringOrInt { /// fn to_output(&self) -> Value { /// match self { /// Self::String(s) => Value::scalar(s.to_owned()), /// Self::Int(i) => Value::scalar(*i), /// } /// } /// /// fn from_input(v: &InputValue) -> Result /// where /// S: ScalarValue /// { /// v.as_string_value() /// .map(|s| Self::String(s.into())) /// .or_else(|| v.as_int_value().map(Self::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// /// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult /// where /// S: ScalarValue /// { /// >::from_str(value) /// .or_else(|_| >::from_str(value)) /// } /// } /// # /// # fn main() {} /// ``` /// /// At the same time, any custom function still may be specified separately: /// ```rust /// # use juniper::{ /// # GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, /// # ScalarToken, Value /// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql( /// with = string_or_int, /// parse_token(String, i32) /// )] /// enum StringOrInt { /// String(String), /// Int(i32), /// } /// /// mod string_or_int { /// use super::*; /// /// pub(super) fn to_output(v: &StringOrInt) -> Value /// where /// S: ScalarValue, /// { /// match v { /// StringOrInt::String(s) => Value::scalar(s.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// /// pub(super) fn from_input(v: &InputValue) -> Result /// where /// S: ScalarValue, /// { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.into())) /// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// /// // No need in `parse_token()` function. /// } /// # /// # fn main() {} /// ``` /// /// # Custom `ScalarValue` /// /// By default, this macro generates code, which is generic over a /// [`ScalarValue`] type. Concrete [`ScalarValue`] type may be specified via /// `#[graphql(scalar = )]` attribute. /// /// It also may be used to provide additional bounds to the [`ScalarValue`] /// generic, like the following: `#[graphql(scalar = S: Trait)]`. /// /// # Additional arbitrary trait bounds /// /// [GraphQL scalar][0] type implementation may be bound with any additional /// trait bounds via `#[graphql(where())]` attribute, like the /// following: `#[graphql(where(S: Trait, Self: fmt::Debug + fmt::Display))]`. /// /// [0]: https://spec.graphql.org/October2021#sec-Scalars /// [1]: https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html /// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_derive(GraphQLScalar, attributes(graphql))] pub fn derive_scalar(input: TokenStream) -> TokenStream { diagnostic::entry_point(|| { graphql_scalar::derive::expand(input.into()) .unwrap_or_abort() .into() }) } /// `#[graphql_scalar]` macro.is interchangeable with /// `#[derive(`[`GraphQLScalar`]`)]` macro, and is used for deriving a /// [GraphQL scalar][0] implementation. /// /// ```rust /// # use juniper::graphql_scalar; /// # /// /// Doc comments are used for the GraphQL type description. /// #[graphql_scalar] /// #[graphql( /// // Custom GraphQL name. /// name = "MyUserId", /// // Description can also specified in the attribute. /// // This will the doc comment, if one exists. /// description = "...", /// // Optional specification URL. /// specified_by_url = "https://tools.ietf.org/html/rfc4122", /// // Explicit generic scalar. /// scalar = S: juniper::ScalarValue, /// transparent, /// )] /// struct UserId(String); /// ``` /// /// # Foreign types /// /// Additionally, `#[graphql_scalar]` can be used directly on foreign types via /// type alias, without using the [newtype pattern][1]. /// /// > __NOTE:__ To satisfy [orphan rules] you should provide local /// > [`ScalarValue`] implementation. /// /// ```rust /// # mod date { /// # use std::{fmt, str::FromStr}; /// # /// # pub struct Date; /// # /// # impl FromStr for Date { /// # type Err = String; /// # /// # fn from_str(_: &str) -> Result { /// # unimplemented!() /// # } /// # } /// # /// # impl fmt::Display for Date { /// # fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { /// # unimplemented!() /// # } /// # } /// # } /// # /// # use juniper::DefaultScalarValue as CustomScalarValue; /// use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; /// /// #[graphql_scalar] /// #[graphql( /// with = date_scalar, /// parse_token(String), /// scalar = CustomScalarValue, /// )] /// // ^^^^^^^^^^^^^^^^^ local `ScalarValue` implementation /// type Date = date::Date; /// // ^^^^^^^^^^ type from another crate /// /// mod date_scalar { /// use super::*; /// /// pub(super) fn to_output(v: &Date) -> 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| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) /// } /// } /// # /// # fn main() {} /// ``` /// /// [0]: https://spec.graphql.org/October2021#sec-Scalars /// [1]: https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html /// [orphan rules]: https://bit.ly/3glAGC2 /// [`GraphQLScalar`]: juniper::GraphQLScalar /// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_attribute] pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { diagnostic::entry_point_with_preserved_body(body.clone(), || { graphql_scalar::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() }) } /// `#[derive(ScalarValue)]` macro for deriving a [`ScalarValue`] /// implementation. /// /// To derive a [`ScalarValue`] on enum you should mark the corresponding enum /// variants with `as_int`, `as_float`, `as_string`, `into_string`, `as_str` and /// `as_bool` attribute argumentes (names correspond to [`ScalarValue`] required /// methods). /// /// ```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( /// into_string, /// as_str, /// as_string = String::clone, /// )] /// // ^^^^^^^^^^^^^ custom resolvers may be provided /// 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) /// } /// } /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_derive(ScalarValue, attributes(value))] pub fn derive_scalar_value(input: TokenStream) -> TokenStream { diagnostic::entry_point(|| { scalar_value::expand_derive(input.into()) .unwrap_or_abort() .into() }) } /// `#[graphql_interface]` macro for generating a [GraphQL interface][1] /// implementation for traits and its implementers. /// /// Specifying multiple `#[graphql_interface]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// /// [GraphQL interfaces][1] are more like structurally-typed interfaces, while /// Rust's traits are more like type classes. Using `impl Trait` isn't an /// option, so you have to cover all trait's methods with type's fields or /// impl block. /// /// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to /// concrete implementers_, while in Rust, a trait is an _abstraction only_ and /// you need a separate type to downcast into a concrete implementer, like enum /// or [trait object][3], because trait doesn't represent a type itself. /// Macro uses Rust enums only to represent a value type of a /// [GraphQL interface][1]. /// /// [GraphQL interface][1] can be represented with struct in case methods don't /// have any arguments: /// /// ```rust /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. /// #[graphql_interface] /// #[graphql(for = Human)] // enumerating all implementers is mandatory /// struct Character { /// id: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { /// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } /// ``` /// /// Also [GraphQL interface][1] can be represented with trait: /// /// ```rust /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. /// #[graphql_interface] /// #[graphql(for = Human)] // enumerating all implementers is mandatory /// trait Character { /// fn id(&self) -> &str; /// } /// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { /// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } /// ``` /// /// > __NOTE:__ Struct or trait representing interface acts only as a blueprint /// > for names of methods, their arguments and return type, so isn't /// > actually used at a runtime. But no-one is stopping you from /// > implementing trait manually for your own usage. /// /// # Custom name, description, deprecation and argument defaults /// /// The name of [GraphQL interface][1], its field, or a field argument may be overridden with a /// `name` attribute's argument. By default, a type name is used or `camelCased` method/argument /// name. /// /// The description of [GraphQL interface][1], its field, or a field argument may be specified /// either with a `description`/`desc` attribute's argument, or with a regular Rust doc comment. /// /// A field of [GraphQL interface][1] may be deprecated by specifying a `deprecated` attribute's /// argument, or with regular Rust `#[deprecated]` attribute. /// /// The default value of a field argument may be specified with a `default` attribute argument (if /// no exact value is specified then [`Default::default`] is used). /// /// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface] /// #[graphql(name = "Character", desc = "Possible episode characters.")] /// trait Chrctr { /// #[graphql(name = "id", desc = "ID of the character.")] /// #[graphql(deprecated = "Don't use it")] /// fn some_id( /// &self, /// #[graphql(name = "number", desc = "Arbitrary number.")] /// #[graphql(default = 5)] /// num: i32, /// ) -> &str; /// } /// /// // NOTICE: Rust docs are used as GraphQL description. /// /// Possible episode characters. /// #[graphql_interface] /// trait CharacterWithDocs { /// /// ID of the character. /// #[deprecated] /// fn id(&self, #[graphql(default)] num: i32) -> &str; /// } /// ``` /// /// # Interfaces implementing other interfaces /// /// GraphQL allows implementing interfaces on other interfaces in addition to /// objects. /// /// > __NOTE:__ Every interface has to specify all other interfaces/objects it /// > implements or is implemented for. Missing one of `for = ` or /// > `impl = ` attributes is an understandable compile-time error. /// /// ```rust /// # extern crate juniper; /// use juniper::{graphql_interface, graphql_object, ID}; /// /// #[graphql_interface] /// #[graphql(for = [HumanValue, Luke])] /// struct Node { /// id: ID, /// } /// /// #[graphql_interface] /// #[graphql(impl = NodeValue, for = Luke)] /// struct Human { /// id: ID, /// home_planet: String, /// } /// /// struct Luke { /// id: ID, /// } /// /// #[graphql_object] /// #[graphql(impl = [HumanValue, NodeValue])] /// impl Luke { /// fn id(&self) -> &ID { /// &self.id /// } /// /// // As `String` and `&str` aren't distinguished by /// // GraphQL spec, you can use them interchangeably. /// // Same is applied for `Cow<'a, str>`. /// // ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ /// fn home_planet() -> &'static str { /// "Tatooine" /// } /// } /// ``` /// /// # GraphQL subtyping and additional `null`able fields /// /// GraphQL allows implementers (both objects and other interfaces) to return /// "subtypes" instead of an original value. Basically, this allows you to /// impose additional bounds on the implementation. /// /// Valid "subtypes" are: /// - interface implementer instead of an interface itself: /// - `I implements T` in place of a `T`; /// - `Vec` in place of a `Vec`. /// - non-`null` value in place of a `null`able: /// - `T` in place of a `Option`; /// - `Vec` in place of a `Vec>`. /// /// These rules are recursively applied, so `Vec>` is a /// valid "subtype" of a `Option>>>>`. /// /// Also, GraphQL allows implementers to add `null`able fields, which aren't /// present on an original interface. /// /// ```rust /// # extern crate juniper; /// use juniper::{graphql_interface, graphql_object, ID}; /// /// #[graphql_interface] /// #[graphql(for = [HumanValue, Luke])] /// struct Node { /// id: ID, /// } /// /// #[graphql_interface] /// #[graphql(for = HumanConnectionValue)] /// struct Connection { /// nodes: Vec, /// } /// /// #[graphql_interface] /// #[graphql(impl = NodeValue, for = Luke)] /// struct Human { /// id: ID, /// home_planet: String, /// } /// /// #[graphql_interface] /// #[graphql(impl = ConnectionValue)] /// struct HumanConnection { /// nodes: Vec, /// // ^^^^^^^^^^ notice not `NodeValue` /// // This can happen, because every `Human` is a `Node` too, so we are /// // just imposing additional bounds, which still can be resolved with /// // `... on Connection { nodes }`. /// } /// /// struct Luke { /// id: ID, /// } /// /// #[graphql_object] /// #[graphql(impl = [HumanValue, NodeValue])] /// impl Luke { /// fn id(&self) -> &ID { /// &self.id /// } /// /// fn home_planet(language: Option) -> &'static str { /// // ^^^^^^^^^^^^^^ /// // Notice additional `null`able field, which is missing on `Human`. /// // Resolving `...on Human { homePlanet }` will provide `None` for /// // this argument. /// match language.as_deref() { /// None | Some("en") => "Tatooine", /// Some("ko") => "타투인", /// _ => todo!(), /// } /// } /// } /// # /// # fn main() {} /// ``` /// /// # Renaming policy /// /// By default, all [GraphQL interface][1] fields and their arguments are renamed /// via `camelCase` policy (so `fn my_id(&self) -> String` becomes `myId` field /// in GraphQL schema, and so on). This complies with default GraphQL naming /// conventions [demonstrated in spec][0]. /// /// However, if you need for some reason apply another naming convention, it's /// possible to do by using `rename_all` attribute's argument. At the moment it /// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, /// `none` (disables any renaming). /// /// ```rust /// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface] /// #[graphql(for = Human, rename_all = "none")] // disables renaming /// trait Character { /// // NOTICE: In the generated GraphQL schema this field and its argument /// // will be `detailed_info` and `info_kind`. /// fn detailed_info(&self, info_kind: String) -> String; /// } /// /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[graphql_object] /// #[graphql(impl = CharacterValue, rename_all = "none")] /// impl Human { /// fn id(&self) -> &str { /// &self.id /// } /// /// fn home_planet(&self) -> &str { /// &self.home_planet /// } /// /// // You can return `&str` even if trait definition returns `String`. /// fn detailed_info(&self, info_kind: String) -> &str { /// (info_kind == "planet") /// .then_some(&self.home_planet) /// .unwrap_or(&self.id) /// } /// } /// ``` /// /// # Ignoring trait methods /// /// To omit some trait method to be assumed as a [GraphQL interface][1] field /// and ignore it, use an `ignore` attribute's argument directly on that method. /// /// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface] /// trait Character { /// fn id(&self) -> &str; /// /// #[graphql(ignore)] /// fn kaboom(&mut self); /// } /// ``` /// /// # Custom context /// /// By default, the generated implementation tries to infer [`Context`] type from signatures of /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments. /// /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly /// with `context` attribute's argument. /// /// If trait method represents a [GraphQL interface][1] field and its argument is named as `context` /// or `ctx` then this argument is assumed as [`Context`] and will be omitted in GraphQL schema. /// Additionally, any argument may be marked as [`Context`] with a `context` attribute's argument. /// /// ```rust /// # use std::collections::HashMap; /// # use juniper::{graphql_interface, graphql_object}; /// # /// struct Database { /// humans: HashMap, /// droids: HashMap, /// } /// impl juniper::Context for Database {} /// /// #[graphql_interface] /// #[graphql(for = [Human, Droid], Context = Database)] /// trait Character { /// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str>; /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>; /// } /// /// struct Human { /// id: String, /// home_planet: String, /// } /// #[graphql_object] /// #[graphql(impl = CharacterValue, Context = Database)] /// impl Human { /// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> { /// context.humans.get(&self.id).map(|h| h.id.as_str()) /// } /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.humans.get(&self.id).map(|h| h.home_planet.as_str()) /// } /// fn home_planet(&self) -> &str { /// &self.home_planet /// } /// } /// /// struct Droid { /// id: String, /// primary_function: String, /// } /// #[graphql_object] /// #[graphql(impl = CharacterValue, Context = Database)] /// impl Droid { /// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> { /// ctx.droids.get(&self.id).map(|h| h.id.as_str()) /// } /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.droids.get(&self.id).map(|h| h.primary_function.as_str()) /// } /// fn primary_function(&self) -> &str { /// &self.primary_function /// } /// } /// ``` /// /// # Using `Executor` /// /// If an [`Executor`] is required in a trait method to resolve a [GraphQL interface][1] field, /// specify it as an argument named as `executor` or explicitly marked with an `executor` /// attribute's argument. Such method argument will be omitted in GraphQL schema. /// /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// /// ```rust /// # use juniper::{graphql_interface, graphql_object, Executor, ScalarValue}; /// # /// #[graphql_interface] /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql(for = Human, scalar = S)] /// trait Character { /// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; /// /// fn name<'b>( /// &'b self, /// #[graphql(executor)] another: &Executor<'_, '_, (), S>, /// ) -> &'b str; /// } /// /// struct Human { /// id: String, /// name: String, /// } /// #[graphql_object] /// #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] /// impl Human { /// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where /// S: ScalarValue, /// { /// executor.look_ahead().field_name() /// } /// /// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str { /// &self.name /// } /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, `#[graphql_interface]` macro generates code, which is generic /// over a [`ScalarValue`] type. This may introduce a problem when at least one /// of [GraphQL interface][1] implementers is restricted to a concrete /// [`ScalarValue`] type in its implementation. To resolve such problem, a /// concrete [`ScalarValue`] type should be specified with a `scalar` /// attribute's argument. /// /// ```rust /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// #[graphql_interface] /// // NOTICE: Removing `Scalar` argument will fail compilation. /// #[graphql(for = Human, scalar = DefaultScalarValue)] /// trait Character { /// fn id(&self) -> &str; /// } /// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, /// } /// ``` /// /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021 /// [1]: https://spec.graphql.org/October2021#sec-Interfaces /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html #[proc_macro_attribute] pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { diagnostic::entry_point_with_preserved_body(body.clone(), || { self::graphql_interface::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() }) } /// `#[derive(GraphQLInterface)]` macro for generating a [GraphQL interface][1] /// implementation for traits and its implementers. /// /// This macro is applicable only to structs and useful in case [interface][1] /// fields don't have any arguments: /// /// ```rust /// use juniper::{GraphQLInterface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. /// #[derive(GraphQLInterface)] /// #[graphql(for = Human)] // enumerating all implementers is mandatory /// struct Character { /// id: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { /// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } /// ``` /// /// For more info and possibilities see [`#[graphql_interface]`][0] macro. /// /// [0]: crate::graphql_interface /// [1]: https://spec.graphql.org/October2021#sec-Interfaces #[proc_macro_derive(GraphQLInterface, attributes(graphql))] pub fn derive_interface(body: TokenStream) -> TokenStream { diagnostic::entry_point(|| { self::graphql_interface::derive::expand(body.into()) .unwrap_or_abort() .into() }) } /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// /// The `#[graphql]` helper attribute is used for configuring the derived /// implementation. Specifying multiple `#[graphql]` attributes on the same /// definition is totally okay. They all will be treated as a single attribute. /// /// ``` /// use juniper::GraphQLObject; /// /// #[derive(GraphQLObject)] /// struct Query { /// // NOTICE: By default, field names will be converted to `camelCase`. /// // In the generated GraphQL schema this field will be available /// // as `apiVersion`. /// api_version: &'static str, /// } /// ``` /// /// # Custom name, description and deprecation /// /// The name of [GraphQL object][1] or its field may be overridden with a `name` /// attribute's argument. By default, a type name is used or `camelCased` field /// name. /// /// The description of [GraphQL object][1] or its field may be specified either /// with a `description`/`desc` attribute's argument, or with a regular Rust doc /// comment. /// /// A field of [GraphQL object][1] may be deprecated by specifying a /// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` /// attribute. /// /// ``` /// # use juniper::GraphQLObject; /// # /// #[derive(GraphQLObject)] /// #[graphql( /// // Rename the type for GraphQL by specifying the name here. /// name = "Human", /// // You may also specify a description here. /// // If present, doc comments will be ignored. /// desc = "Possible episode human.", /// )] /// struct HumanWithAttrs { /// #[graphql(name = "id", desc = "ID of the human.")] /// #[graphql(deprecated = "Don't use it")] /// some_id: String, /// } /// /// // Rust docs are used as GraphQL description. /// /// Possible episode human. /// #[derive(GraphQLObject)] /// struct HumanWithDocs { /// // Doc comments also work on fields. /// /// ID of the human. /// #[deprecated] /// id: String, /// } /// ``` /// /// # Renaming policy /// /// By default, all [GraphQL object][1] fields are renamed via `camelCase` /// policy (so `api_version: String` becomes `apiVersion` field in GraphQL /// schema, and so on). This complies with default GraphQL naming conventions /// [demonstrated in spec][0]. /// /// However, if you need for some reason apply another naming convention, it's /// possible to do by using `rename_all` attribute's argument. At the moment it /// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, /// `none` (disables any renaming). /// /// ``` /// # use juniper::GraphQLObject; /// # /// #[derive(GraphQLObject)] /// #[graphql(rename_all = "none")] // disables renaming /// struct Query { /// // NOTICE: In the generated GraphQL schema this field will be available /// // as `api_version`. /// api_version: String, /// } /// ``` /// /// # Ignoring struct fields /// /// To omit exposing a struct field in the GraphQL schema, use an `ignore` /// attribute's argument directly on that field. /// /// ``` /// # use juniper::GraphQLObject; /// # /// #[derive(GraphQLObject)] /// struct Human { /// id: String, /// #[graphql(ignore)] /// home_planet: String, /// #[graphql(skip)] /// // ^^^^ alternative naming, up to your preference /// password_hash: String, /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, `#[derive(GraphQLObject)]` macro generates code, which is /// generic over a [`ScalarValue`] type. This may introduce a problem when at /// least one of its fields is restricted to a concrete [`ScalarValue`] type in /// its implementation. To resolve such problem, a concrete [`ScalarValue`] type /// should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{DefaultScalarValue, GraphQLObject}; /// # /// #[derive(GraphQLObject)] /// // NOTICE: Removing `scalar` argument will fail compilation. /// #[graphql(scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// helper: Droid, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(scalar = DefaultScalarValue)] /// struct Droid { /// id: String, /// } /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Objects #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(body: TokenStream) -> TokenStream { diagnostic::entry_point(|| { self::graphql_object::derive::expand(body.into()) .unwrap_or_abort() .into() }) } /// `#[graphql_object]` macro for generating a [GraphQL object][1] /// implementation for structs with computable field resolvers (declared via /// a regular Rust `impl` block). /// /// It enables you to write GraphQL field resolvers for a type by declaring a /// regular Rust `impl` block. Under the hood, the macro implements /// [`GraphQLType`]/[`GraphQLValue`] traits. /// /// Specifying multiple `#[graphql_object]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// /// ``` /// use juniper::graphql_object; /// /// // We can declare the type as a plain struct without any members. /// struct Query; /// /// #[graphql_object] /// impl Query { /// // WARNING: Only GraphQL fields can be specified in this `impl` block. /// // If normal methods are required on the struct, they can be /// // defined either in a separate "normal" `impl` block, or /// // marked with `#[graphql(ignore)]` attribute. /// /// // This defines a simple, static field which does not require any /// // context. /// // Such field can return any value that implements `GraphQLType` and /// // `GraphQLValue` traits. /// // /// // NOTICE: By default, field names will be converted to `camelCase`. /// // In the generated GraphQL schema this field will be available /// // as `apiVersion`. /// fn api_version() -> &'static str { /// "0.1" /// } /// /// // This field takes two arguments. /// // GraphQL arguments are just regular function parameters. /// // /// // NOTICE: In `juniper`, arguments are non-nullable by default. For /// // optional arguments, you have to specify them as `Option<_>`. /// async fn add(a: f64, b: f64, c: Option) -> f64 { /// a + b + c.unwrap_or(0.0) /// } /// } /// ``` /// /// # Accessing self /// /// Fields may also have a `self` receiver. /// /// ``` /// # use juniper::graphql_object; /// # /// struct Person { /// first_name: String, /// last_name: String, /// } /// /// #[graphql_object] /// impl Person { /// fn first_name(&self) -> &str { /// &self.first_name /// } /// /// fn last_name(&self) -> &str { /// &self.last_name /// } /// /// fn full_name(&self) -> String { /// self.build_full_name() /// } /// /// // This method is useful only to define GraphQL fields, but is not /// // a field itself, so we ignore it in schema. /// #[graphql(ignore)] // or `#[graphql(skip)]`, up to your preference /// fn build_full_name(&self) -> String { /// format!("{} {}", self.first_name, self.last_name) /// } /// } /// ``` /// /// # Custom name, description, deprecation and argument defaults /// /// The name of [GraphQL object][1], its field, or a field argument may be /// overridden with a `name` attribute's argument. By default, a type name is /// used or `camelCased` method/argument name. /// /// The description of [GraphQL object][1], its field, or a field argument may /// be specified either with a `description`/`desc` attribute's argument, or /// with a regular Rust doc comment. /// /// A field of [GraphQL object][1] may be deprecated by specifying a /// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` /// attribute. /// /// The default value of a field argument may be specified with a `default` /// attribute argument (if no exact value is specified then [`Default::default`] /// is used). /// /// ``` /// # use juniper::graphql_object; /// # /// struct HumanWithAttrs; /// /// #[graphql_object] /// #[graphql( /// // Rename the type for GraphQL by specifying the name here. /// name = "Human", /// // You may also specify a description here. /// // If present, doc comments will be ignored. /// desc = "Possible episode human.", /// )] /// impl HumanWithAttrs { /// #[graphql(name = "id", desc = "ID of the human.")] /// #[graphql(deprecated = "Don't use it")] /// fn some_id( /// &self, /// #[graphql(name = "number", desc = "Arbitrary number.")] /// // You may specify default values. /// // A default can be any valid expression that yields the right type. /// #[graphql(default = 5)] /// num: i32, /// ) -> &str { /// "Don't use me!" /// } /// } /// /// struct HumanWithDocs; /// /// // Rust docs are used as GraphQL description. /// /// Possible episode human. /// #[graphql_object] /// impl HumanWithDocs { /// // Doc comments also work on fields. /// /// ID of the human. /// #[deprecated] /// fn id( /// &self, /// // If expression is not specified then `Default::default()` is used. /// #[graphql(default)] num: i32, /// ) -> &str { /// "Deprecated" /// } /// } /// ``` /// /// # Renaming policy /// /// By default, all [GraphQL object][1] fields and their arguments are renamed /// via `camelCase` policy (so `fn api_version() -> String` becomes `apiVersion` /// field in GraphQL schema, and so on). This complies with default GraphQL /// naming conventions [demonstrated in spec][0]. /// /// However, if you need for some reason apply another naming convention, it's /// possible to do by using `rename_all` attribute's argument. At the moment it /// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, /// `none` (disables any renaming). /// /// ``` /// # use juniper::graphql_object; /// # /// struct Query; /// /// #[graphql_object] /// #[graphql(rename_all = "none")] // disables renaming /// impl Query { /// // NOTICE: In the generated GraphQL schema this field will be available /// // as `api_version`. /// fn api_version() -> &'static str { /// "0.1" /// } /// /// // NOTICE: In the generated GraphQL schema these field arguments will be /// // available as `arg_a` and `arg_b`. /// async fn add(arg_a: f64, arg_b: f64, c: Option) -> f64 { /// arg_a + arg_b + c.unwrap_or(0.0) /// } /// } /// ``` /// /// # Ignoring methods /// /// To omit some method to be assumed as a [GraphQL object][1] field and ignore /// it, use an `ignore` attribute's argument directly on that method. /// /// ``` /// # use juniper::graphql_object; /// # /// struct Human(String); /// /// #[graphql_object] /// impl Human { /// fn id(&self) -> &str { /// &self.0 /// } /// /// #[graphql(ignore)] /// fn kaboom(&mut self) {} /// } /// ``` /// /// # Custom context /// /// By default, the generated implementation tries to infer [`Context`] type /// from signatures of `impl` block methods, and uses [unit type `()`][4] if /// signatures contains no [`Context`] arguments. /// /// If [`Context`] type cannot be inferred or is inferred incorrectly, then /// specify it explicitly with `context` attribute's argument. /// /// If method argument is named as `context` or `ctx` then this argument is /// assumed as [`Context`] and will be omitted in GraphQL schema. /// Additionally, any argument may be marked as [`Context`] with a `context` /// attribute's argument. /// /// ``` /// # use std::collections::HashMap; /// # use juniper::graphql_object; /// # /// struct Database { /// humans: HashMap, /// } /// impl juniper::Context for Database {} /// /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[graphql_object] /// #[graphql(context = Database)] /// impl Human { /// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> { /// context.humans.get(&self.id).map(|h| h.id.as_str()) /// } /// fn info<'db>(&self, context: &'db Database) -> Option<&'db str> { /// context.humans.get(&self.id).map(|h| h.home_planet.as_str()) /// } /// } /// ``` /// /// # Using `Executor` /// /// If an [`Executor`] is required in a method to resolve a [GraphQL object][1] /// field, specify it as an argument named as `executor` or explicitly marked /// with an `executor` attribute's argument. Such method argument will be /// omitted in GraphQL schema. /// /// However, this requires to explicitly parametrize over [`ScalarValue`], as /// [`Executor`] does so. /// /// ``` /// # use juniper::{graphql_object, Executor, GraphQLObject, ScalarValue}; /// # /// struct Human { /// name: String, /// } /// /// #[graphql_object] /// // NOTICE: Specifying `ScalarValue` as custom named type parameter. /// // Its name should be similar to the one used in methods. /// #[graphql(scalar = S: ScalarValue)] /// impl Human { /// async fn id<'a, S: ScalarValue>( /// &self, /// executor: &'a Executor<'_, '_, (), S>, /// ) -> &'a str { /// executor.look_ahead().field_name() /// } /// /// fn name<'b, S: ScalarValue>( /// &'b self, /// #[graphql(executor)] _another: &Executor<'_, '_, (), S>, /// ) -> &'b str { /// &self.name /// } /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, `#[graphql_object]` macro generates code, which is generic over /// a [`ScalarValue`] type. This may introduce a problem when at least one of /// its fields is restricted to a concrete [`ScalarValue`] type in its /// implementation. To resolve such problem, a concrete [`ScalarValue`] type /// should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{graphql_object, DefaultScalarValue, GraphQLObject}; /// # /// struct Human(String); /// /// #[graphql_object] /// // NOTICE: Removing `scalar` argument will fail compilation. /// #[graphql(scalar = DefaultScalarValue)] /// impl Human { /// fn id(&self) -> &str { /// &self.0 /// } /// /// fn helper(&self) -> Droid { /// Droid { /// id: self.0.clone(), /// } /// } /// } /// /// #[derive(GraphQLObject)] /// #[graphql(scalar = DefaultScalarValue)] /// struct Droid { /// id: String, /// } /// ``` /// /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor /// [`GraphQLType`]: juniper::GraphQLType /// [`GraphQLValue`]: juniper::GraphQLValue /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/October2021 /// [1]: https://spec.graphql.org/October2021#sec-Objects #[proc_macro_attribute] pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { diagnostic::entry_point_with_preserved_body(body.clone(), || { self::graphql_object::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() }) } /// `#[graphql_subscription]` macro for generating a [GraphQL subscription][1] /// implementation for structs with computable field resolvers (declared via /// a regular Rust `impl` block). /// /// It enables you to write GraphQL field resolvers for a type by declaring a /// regular Rust `impl` block. Under the hood, the macro implements /// [`GraphQLType`]/[`GraphQLSubscriptionValue`] traits. /// /// Specifying multiple `#[graphql_subscription]` attributes on the same /// definition is totally okay. They all will be treated as a single attribute. /// /// This macro is similar to [`#[graphql_object]` macro](macro@graphql_object) /// and has all its properties, but requires methods to be `async` and return /// [`Stream`] of values instead of a value itself. /// /// ``` /// # use futures::stream::{self, BoxStream}; /// use juniper::graphql_subscription; /// /// // We can declare the type as a plain struct without any members. /// struct Subscription; /// /// #[graphql_subscription] /// impl Subscription { /// // WARNING: Only GraphQL fields can be specified in this `impl` block. /// // If normal methods are required on the struct, they can be /// // defined either in a separate "normal" `impl` block, or /// // marked with `#[graphql(ignore)]` attribute. /// /// // This defines a simple, static field which does not require any /// // context. /// // Such field can return a `Stream` of any value implementing /// // `GraphQLType` and `GraphQLValue` traits. /// // /// // NOTICE: Method must be `async`. /// async fn api_version() -> BoxStream<'static, &'static str> { /// Box::pin(stream::once(async { "0.1" })) /// } /// } /// ``` /// /// [`GraphQLType`]: juniper::GraphQLType /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue /// [`Stream`]: futures::Stream /// [1]: https://spec.graphql.org/October2021#sec-Subscription #[proc_macro_attribute] pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { diagnostic::entry_point_with_preserved_body(body.clone(), || { self::graphql_subscription::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() }) } /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and /// structs. /// /// The `#[graphql]` helper attribute is used for configuring the derived implementation. Specifying /// multiple `#[graphql]` attributes on the same definition is totally okay. They all will be /// treated as a single attribute. /// /// ``` /// use derive_more::From; /// use juniper::{GraphQLObject, GraphQLUnion}; /// /// #[derive(GraphQLObject)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// #[derive(From, GraphQLUnion)] /// enum CharacterEnum { /// Human(Human), /// Droid(Droid), /// } /// ``` /// /// # Custom name and description /// /// The name of [GraphQL union][1] may be overriden with a `name` attribute's argument. By default, /// a type name is used. /// /// The description of [GraphQL union][1] may be specified either with a `description`/`desc` /// attribute's argument, or with a regular Rust doc comment. /// /// ``` /// # use juniper::{GraphQLObject, GraphQLUnion}; /// # /// # #[derive(GraphQLObject)] /// # struct Human { /// # id: String, /// # home_planet: String, /// # } /// # /// # #[derive(GraphQLObject)] /// # struct Droid { /// # id: String, /// # primary_function: String, /// # } /// # /// #[derive(GraphQLUnion)] /// #[graphql(name = "Character", desc = "Possible episode characters.")] /// enum Chrctr { /// Human(Human), /// Droid(Droid), /// } /// /// // NOTICE: Rust docs are used as GraphQL description. /// /// Possible episode characters. /// #[derive(GraphQLUnion)] /// enum CharacterWithDocs { /// Human(Human), /// Droid(Droid), /// } /// /// // NOTICE: `description` argument takes precedence over Rust docs. /// /// Not a GraphQL description anymore. /// #[derive(GraphQLUnion)] /// #[graphql(description = "Possible episode characters.")] /// enum CharacterWithDescription { /// Human(Human), /// Droid(Droid), /// } /// ``` /// /// # Custom context /// /// By default, the generated implementation uses [unit type `()`][4] as [`Context`]. To use a /// custom [`Context`] type for [GraphQL union][1] variants types or external resolver functions, /// specify it with `context` attribute's argument. /// /// ``` /// # use juniper::{GraphQLObject, GraphQLUnion}; /// # /// #[derive(GraphQLObject)] /// #[graphql(Context = CustomContext)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(Context = CustomContext)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// pub struct CustomContext; /// impl juniper::Context for CustomContext {} /// /// #[derive(GraphQLUnion)] /// #[graphql(Context = CustomContext)] /// enum Character { /// Human(Human), /// Droid(Droid), /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, this macro generates code, which is generic over a /// [`ScalarValue`] type. This may introduce a problem when at least one of /// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type /// in its implementation. To resolve such problem, a concrete [`ScalarValue`] /// type should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion}; /// # /// #[derive(GraphQLObject)] /// #[graphql(scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// // NOTICE: Removing `Scalar` argument will fail compilation. /// #[derive(GraphQLUnion)] /// #[graphql(scalar = DefaultScalarValue)] /// enum Character { /// Human(Human), /// Droid(Droid), /// } /// ``` /// /// # Ignoring enum variants /// /// To omit exposing an enum variant in the GraphQL schema, use an `ignore` /// attribute's argument directly on that variant. /// /// > __WARNING__: /// > It's the _library user's responsibility_ to ensure that ignored enum variant is _never_ /// > returned from resolvers, otherwise resolving the GraphQL query will __panic at runtime__. /// /// ``` /// # use std::marker::PhantomData; /// use derive_more::From; /// use juniper::{GraphQLObject, GraphQLUnion}; /// /// #[derive(GraphQLObject)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// #[derive(From, GraphQLUnion)] /// enum Character { /// Human(Human), /// Droid(Droid), /// #[from(ignore)] /// #[graphql(ignore)] /// _State(PhantomData), /// } /// ``` /// /// # External resolver functions /// /// To use a custom logic for resolving a [GraphQL union][1] variant, an external resolver function /// may be specified with: /// - either a `with` attribute's argument on an enum variant; /// - or an `on` attribute's argument on an enum/struct itself. /// /// ``` /// # use juniper::{GraphQLObject, GraphQLUnion}; /// # /// #[derive(GraphQLObject)] /// #[graphql(Context = CustomContext)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(Context = CustomContext)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// pub struct CustomContext { /// droid: Droid, /// } /// impl juniper::Context for CustomContext {} /// /// #[derive(GraphQLUnion)] /// #[graphql(Context = CustomContext)] /// enum Character { /// Human(Human), /// #[graphql(with = Character::droid_from_context)] /// Droid(Droid), /// } /// /// impl Character { /// // NOTICE: The function signature must contain `&self` and `&Context`, /// // and return `Option<&VariantType>`. /// fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> { /// Some(&ctx.droid) /// } /// } /// /// #[derive(GraphQLUnion)] /// #[graphql(Context = CustomContext)] /// #[graphql(on Droid = CharacterWithoutDroid::droid_from_context)] /// enum CharacterWithoutDroid { /// Human(Human), /// #[graphql(ignore)] /// Droid, /// } /// /// impl CharacterWithoutDroid { /// fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> { /// if let Self::Droid = self { /// Some(&ctx.droid) /// } else { /// None /// } /// } /// } /// ``` /// /// # Deriving structs /// /// Specifying external resolver functions is mandatory for using a struct as a [GraphQL union][1], /// because this is the only way to declare [GraphQL union][1] variants in this case. /// /// ``` /// # use std::collections::HashMap; /// # use juniper::{GraphQLObject, GraphQLUnion}; /// # /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// struct Database { /// humans: HashMap, /// droids: HashMap, /// } /// impl juniper::Context for Database {} /// /// #[derive(GraphQLUnion)] /// #[graphql( /// Context = Database, /// on Human = Character::get_human, /// on Droid = Character::get_droid, /// )] /// struct Character { /// id: String, /// } /// /// impl Character { /// fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human>{ /// ctx.humans.get(&self.id) /// } /// /// fn get_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid>{ /// ctx.droids.get(&self.id) /// } /// } /// ``` /// /// [`Context`]: juniper::Context /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Unions /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html #[proc_macro_derive(GraphQLUnion, attributes(graphql))] pub fn derive_union(body: TokenStream) -> TokenStream { diagnostic::entry_point(|| { self::graphql_union::derive::expand(body.into()) .unwrap_or_abort() .into() }) } /// `#[graphql_union]` macro for deriving a [GraphQL union][1] implementation for traits. /// /// Specifying multiple `#[graphql_union]` attributes on the same definition is totally okay. They /// all will be treated as a single attribute. /// /// A __trait has to be [object safe][2]__, because schema resolvers will need to return a /// [trait object][3] to specify a [GraphQL union][1] behind it. The [trait object][3] has to be /// [`Send`] and [`Sync`]. /// /// ``` /// use juniper::{graphql_union, GraphQLObject}; /// /// #[derive(GraphQLObject)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// #[graphql_union] /// trait Character { /// // NOTICE: The method signature must contain `&self` and return `Option<&VariantType>`. /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// } /// /// impl Character for Human { /// fn as_human(&self) -> Option<&Human> { Some(&self) } /// } /// /// impl Character for Droid { /// fn as_droid(&self) -> Option<&Droid> { Some(&self) } /// } /// ``` /// /// # Custom name and description /// /// The name of [GraphQL union][1] may be overriden with a `name` attribute's argument. By default, /// a type name is used. /// /// The description of [GraphQL union][1] may be specified either with a `description`/`desc` /// attribute's argument, or with a regular Rust doc comment. /// /// ``` /// # use juniper::{graphql_union, GraphQLObject}; /// # /// # #[derive(GraphQLObject)] /// # struct Human { /// # id: String, /// # home_planet: String, /// # } /// # /// # #[derive(GraphQLObject)] /// # struct Droid { /// # id: String, /// # primary_function: String, /// # } /// # /// #[graphql_union] /// #[graphql(name = "Character", desc = "Possible episode characters.")] /// trait Chrctr { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// } /// /// // NOTICE: Rust docs are used as GraphQL description. /// /// Possible episode characters. /// trait CharacterWithDocs { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// } /// /// // NOTICE: `description` argument takes precedence over Rust docs. /// /// Not a GraphQL description anymore. /// #[graphql_union] /// #[graphql(description = "Possible episode characters.")] /// trait CharacterWithDescription { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// } /// # /// # impl Chrctr for Human {} /// # impl Chrctr for Droid {} /// # impl CharacterWithDocs for Human {} /// # impl CharacterWithDocs for Droid {} /// # impl CharacterWithDescription for Human {} /// # impl CharacterWithDescription for Droid {} /// ``` /// /// # Custom context /// /// By default, the generated implementation tries to infer [`Context`] type from signatures of /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments. /// /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly /// with `context` attribute's argument. /// /// ``` /// # use std::collections::HashMap; /// # use juniper::{graphql_union, GraphQLObject}; /// # /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// struct Database { /// humans: HashMap, /// droids: HashMap, /// } /// impl juniper::Context for Database {} /// /// #[graphql_union] /// #[graphql(context = Database)] /// trait Character { /// fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { None } /// fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> { None } /// } /// /// impl Character for Human { /// fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { /// ctx.humans.get(&self.id) /// } /// } /// /// impl Character for Droid { /// fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> { /// ctx.droids.get(&self.id) /// } /// } /// ``` /// /// # Custom `ScalarValue` /// /// By default, `#[graphql_union]` macro generates code, which is generic over /// a [`ScalarValue`] type. This may introduce a problem when at least one of /// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type /// in its implementation. To resolve such problem, a concrete [`ScalarValue`] /// type should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{graphql_union, DefaultScalarValue, GraphQLObject}; /// # /// #[derive(GraphQLObject)] /// #[graphql(scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// // NOTICE: Removing `scalar` argument will fail compilation. /// #[graphql_union] /// #[graphql(scalar = DefaultScalarValue)] /// trait Character { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// } /// # /// # impl Character for Human {} /// # impl Character for Droid {} /// ``` /// /// # Ignoring trait methods /// /// To omit some trait method to be assumed as a [GraphQL union][1] variant and /// ignore it, use an `ignore` attribute's argument directly on that method. /// /// ``` /// # use juniper::{graphql_union, GraphQLObject}; /// # /// # #[derive(GraphQLObject)] /// # struct Human { /// # id: String, /// # home_planet: String, /// # } /// # /// # #[derive(GraphQLObject)] /// # struct Droid { /// # id: String, /// # primary_function: String, /// # } /// # /// #[graphql_union] /// trait Character { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } /// #[graphql(ignore)] /// fn id(&self) -> &str; /// } /// # /// # impl Character for Human { /// # fn id(&self) -> &str { self.id.as_str() } /// # } /// # /// # impl Character for Droid { /// # fn id(&self) -> &str { self.id.as_str() } /// # } /// ``` /// /// # External resolver functions /// /// It's not mandatory to use trait methods as [GraphQL union][1] variant resolvers, and instead /// custom functions may be specified with an `on` attribute's argument. /// /// ``` /// # use std::collections::HashMap; /// # use juniper::{graphql_union, GraphQLObject}; /// # /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } /// /// #[derive(GraphQLObject)] /// #[graphql(Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } /// /// struct Database { /// humans: HashMap, /// droids: HashMap, /// } /// impl juniper::Context for Database {} /// /// #[graphql_union] /// #[graphql(context = Database)] /// #[graphql( /// on Human = DynCharacter::get_human, /// on Droid = get_droid, /// )] /// trait Character { /// #[graphql(ignore)] /// fn id(&self) -> &str; /// } /// /// impl Character for Human { /// fn id(&self) -> &str { self.id.as_str() } /// } /// /// impl Character for Droid { /// fn id(&self) -> &str { self.id.as_str() } /// } /// /// // NOTICE: The trait object is always `Send` and `Sync`. /// type DynCharacter<'a> = dyn Character + Send + Sync + 'a; /// /// impl<'a> DynCharacter<'a> { /// fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { /// ctx.humans.get(self.id()) /// } /// } /// /// // NOTICE: Custom resolver function doesn't have to be a method of a type. /// // It's only a matter of the function signature to match the requirements. /// fn get_droid<'db>(ch: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droid> { /// ctx.droids.get(ch.id()) /// } /// ``` /// /// [`Context`]: juniper::Context /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/October2021#sec-Unions /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html #[proc_macro_attribute] pub fn graphql_union(attr: TokenStream, body: TokenStream) -> TokenStream { diagnostic::entry_point_with_preserved_body(body.clone(), || { self::graphql_union::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() }) } juniper_codegen-0.16.0/src/scalar_value/mod.rs000064400000000000000000000415341046102023000174340ustar 00000000000000//! Code generation for `#[derive(ScalarValue)]` macro. use std::collections::HashMap; use proc_macro2::{Literal, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, visit::Visit, }; use crate::common::{ diagnostic, filter_attrs, parse::{attr::err, ParseBufferExt as _}, SpanContainer, }; /// [`diagnostic::Scope`] of errors for `#[derive(ScalarValue)]` macro. const ERR: diagnostic::Scope = diagnostic::Scope::ScalarValueDerive; /// Expands `#[derive(ScalarValue)]` macro into generated code. pub fn expand_derive(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let span = ast.span(); let data_enum = match ast.data { syn::Data::Enum(e) => e, _ => return Err(ERR.custom_error(ast.span(), "can only be derived for enums")), }; let attr = Attr::from_attrs("value", &ast.attrs)?; let mut methods = HashMap::>::new(); for var in data_enum.variants.clone() { let (ident, field) = (var.ident, Field::try_from(var.fields)?); for attr in VariantAttr::from_attrs("value", &var.attrs)?.0 { let (method, expr) = attr.into_inner(); methods.entry(method).or_default().push(Variant { ident: ident.clone(), field: field.clone(), expr, }); } } let missing_methods = [ (Method::AsInt, "as_int"), (Method::AsFloat, "as_float"), (Method::AsStr, "as_str"), (Method::AsString, "as_string"), (Method::IntoString, "into_string"), (Method::AsBool, "as_bool"), ] .iter() .filter_map(|(method, err)| (!methods.contains_key(method)).then_some(err)) .fold(None, |acc, &method| { Some( acc.map(|acc| format!("{acc}, {method}")) .unwrap_or_else(|| method.into()), ) }) .filter(|_| !attr.allow_missing_attrs); if let Some(missing_methods) = missing_methods { return Err(ERR.custom_error( span, format!( "missing `#[value({missing_methods})]` attributes. In case you \ are sure that it's ok, use `#[value(allow_missing_attributes)]` \ to suppress this error.", ), )); } Ok(Definition { ident: ast.ident, generics: ast.generics, variants: data_enum.variants.into_iter().collect(), methods, } .into_token_stream()) } /// Available arguments behind `#[value]` attribute when generating code for /// an enum definition. #[derive(Default)] struct Attr { /// Allows missing [`Method`]s. allow_missing_attrs: bool, } impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Attr::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "allow_missing_attributes" => { out.allow_missing_attrs = true; } name => { return Err(err::unknown_arg(&ident, name)); } }; input.try_parse::()?; } Ok(out) } } impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(mut self, another: Self) -> syn::Result { self.allow_missing_attrs |= another.allow_missing_attrs; Ok(self) } /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a enum variant. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) } } /// Possible attribute names of the `#[derive(ScalarValue)]`. #[derive(Eq, Hash, PartialEq)] enum Method { /// `#[value(as_int)]`. AsInt, /// `#[value(as_float)]`. AsFloat, /// `#[value(as_str)]`. AsStr, /// `#[value(as_string)]`. AsString, /// `#[value(into_string)]`. IntoString, /// `#[value(as_bool)]`. AsBool, } /// Available arguments behind `#[value]` attribute when generating code for an /// enum variant. #[derive(Default)] struct VariantAttr(Vec)>>); impl Parse for VariantAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Vec::new(); while !input.is_empty() { let ident = input.parse::()?; let method = match ident.to_string().as_str() { "as_int" => Method::AsInt, "as_float" => Method::AsFloat, "as_str" => Method::AsStr, "as_string" => Method::AsString, "into_string" => Method::IntoString, "as_bool" => Method::AsBool, name => { return Err(err::unknown_arg(&ident, name)); } }; let expr = input .parse::() .ok() .map(|_| input.parse::()) .transpose()?; out.push(SpanContainer::new( ident.span(), expr.as_ref().map(|e| e.span()), (method, expr), )); input.try_parse::()?; } Ok(VariantAttr(out)) } } impl VariantAttr { /// Tries to merge two [`VariantAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(mut self, mut another: Self) -> syn::Result { let dup = another.0.iter().find(|m| self.0.contains(m)); if let Some(dup) = dup { Err(err::dup_arg(dup.span_ident())) } else { self.0.append(&mut another.0); Ok(self) } } /// Parses [`VariantAttr`] from the given multiple `name`d /// [`syn::Attribute`]s placed on a enum variant. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) } } /// Definition of a [`ScalarValue`] for code generation. /// /// [`ScalarValue`]: juniper::ScalarValue struct Definition { /// [`syn::Ident`] of the enum representing this [`ScalarValue`]. /// /// [`ScalarValue`]: juniper::ScalarValue ident: syn::Ident, /// [`syn::Generics`] of the enum representing this [`ScalarValue`]. /// /// [`ScalarValue`]: juniper::ScalarValue generics: syn::Generics, /// [`syn::Variant`]s of the enum representing this [`ScalarValue`]. /// /// [`ScalarValue`]: juniper::ScalarValue variants: Vec, /// [`Variant`]s marked with a [`Method`] attribute. methods: HashMap>, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_scalar_value_tokens().to_tokens(into); self.impl_from_tokens().to_tokens(into); self.impl_display_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing [`ScalarValue`]. /// /// [`ScalarValue`]: juniper::ScalarValue fn impl_scalar_value_tokens(&self) -> TokenStream { let ident = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); let methods = [ ( Method::AsInt, quote! { fn as_int(&self) -> ::core::option::Option<::core::primitive::i32> }, quote! { ::core::primitive::i32::from(*v) }, ), ( Method::AsFloat, quote! { fn as_float(&self) -> ::core::option::Option<::core::primitive::f64> }, quote! { ::core::primitive::f64::from(*v) }, ), ( Method::AsStr, quote! { fn as_str(&self) -> ::core::option::Option<&::core::primitive::str> }, quote! { ::core::convert::AsRef::as_ref(v) }, ), ( Method::AsString, quote! { fn as_string(&self) -> ::core::option::Option<::std::string::String> }, quote! { ::std::string::ToString::to_string(v) }, ), ( Method::IntoString, quote! { fn into_string(self) -> ::core::option::Option<::std::string::String> }, quote! { ::std::string::String::from(v) }, ), ( Method::AsBool, quote! { fn as_bool(&self) -> ::core::option::Option<::core::primitive::bool> }, quote! { ::core::primitive::bool::from(*v) }, ), ]; let methods = methods.iter().map(|(m, sig, def)| { let arms = self.methods.get(m).into_iter().flatten().map(|v| { let arm = v.match_arm(); let call = v.expr.as_ref().map_or(def.clone(), |f| quote! { #f(v) }); quote! { #arm => ::core::option::Option::Some(#call), } }); quote! { #sig { match self { #(#arms)* _ => ::core::option::Option::None, } } } }); quote! { #[automatically_derived] impl #impl_gens ::juniper::ScalarValue for #ident #ty_gens #where_clause { #( #methods )* } } } /// Returns generated code implementing: /// - [`From`] each variant into enum itself. /// - [`From`] enum into [`Option`] of each variant. /// - [`From`] enum reference into [`Option`] of each variant reference. fn impl_from_tokens(&self) -> TokenStream { let ty_ident = &self.ident; let (impl_gen, ty_gen, where_clause) = self.generics.split_for_impl(); // We don't impose additional bounds on generic parameters, because // `ScalarValue` itself has `'static` bound. let mut generics = self.generics.clone(); generics.params.push(parse_quote! { '___a }); let (lf_impl_gen, _, _) = generics.split_for_impl(); self.variants .iter() .map(|v| { let var_ident = &v.ident; let field = v.fields.iter().next().unwrap(); let var_ty = &field.ty; let var_field = field .ident .as_ref() .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); quote! { #[automatically_derived] impl #impl_gen ::core::convert::From<#var_ty> for #ty_ident #ty_gen #where_clause { fn from(v: #var_ty) -> Self { Self::#var_ident #var_field } } #[automatically_derived] impl #impl_gen ::core::convert::From<#ty_ident #ty_gen> for ::core::option::Option<#var_ty> #where_clause { fn from(ty: #ty_ident #ty_gen) -> Self { if let #ty_ident::#var_ident #var_field = ty { ::core::option::Option::Some(v) } else { ::core::option::Option::None } } } #[automatically_derived] impl #lf_impl_gen ::core::convert::From<&'___a #ty_ident #ty_gen> for ::core::option::Option<&'___a #var_ty> #where_clause { fn from(ty: &'___a #ty_ident #ty_gen) -> Self { if let #ty_ident::#var_ident #var_field = ty { ::core::option::Option::Some(v) } else { ::core::option::Option::None } } } } }) .collect() } /// Returns generated code implementing [`Display`] by matching over each /// enum variant. /// /// [`Display`]: std::fmt::Display fn impl_display_tokens(&self) -> TokenStream { let ident = &self.ident; let mut generics = self.generics.clone(); generics.make_where_clause(); for var in &self.variants { let var_ty = &var.fields.iter().next().unwrap().ty; let mut check = IsVariantGeneric::new(&self.generics); check.visit_type(var_ty); if check.res { generics .where_clause .as_mut() .unwrap() .predicates .push(parse_quote! { #var_ty: ::core::fmt::Display }); } } let (impl_gen, ty_gen, where_clause) = generics.split_for_impl(); let arms = self.variants.iter().map(|v| { let var_ident = &v.ident; let field = v.fields.iter().next().unwrap(); let var_field = field .ident .as_ref() .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); quote! { Self::#var_ident #var_field => ::core::fmt::Display::fmt(v, f), } }); quote! { #[automatically_derived] impl #impl_gen ::core::fmt::Display for #ident #ty_gen #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { match self { #( #arms )* } } } } } } /// Single-[`Field`] enum variant. #[derive(Clone)] struct Variant { /// [`Variant`] [`syn::Ident`]. ident: syn::Ident, /// Single [`Variant`] [`Field`]. field: Field, /// Optional resolver provided by [`VariantAttr`]. expr: Option, } impl Variant { /// Returns generated code for matching over this [`Variant`]. fn match_arm(&self) -> TokenStream { let (ident, field) = (&self.ident, &self.field.match_arg()); quote! { Self::#ident #field } } } /// Enum [`Variant`] field. #[derive(Clone)] enum Field { /// Named [`Field`]. Named(syn::Field), /// Unnamed [`Field`]. Unnamed(syn::Field), } impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Named(f) => f.ident.to_tokens(tokens), Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), } } } impl TryFrom for Field { type Error = syn::Error; fn try_from(value: syn::Fields) -> Result { match value { syn::Fields::Named(mut f) if f.named.len() == 1 => { Ok(Self::Named(f.named.pop().unwrap().into_value())) } syn::Fields::Unnamed(mut f) if f.unnamed.len() == 1 => { Ok(Self::Unnamed(f.unnamed.pop().unwrap().into_value())) } _ => Err(ERR.custom_error(value.span(), "expected exactly 1 field")), } } } impl Field { /// Returns a [`Field`] for constructing or matching over a [`Variant`]. fn match_arg(&self) -> TokenStream { match self { Self::Named(_) => quote! { { #self: v } }, Self::Unnamed(_) => quote! { (v) }, } } } /// [`Visit`]or checking whether a [`Variant`]'s [`Field`] contains generic /// parameters. struct IsVariantGeneric<'a> { /// Indicates whether the checked [`Variant`]'s [`Field`] contains generic /// parameters. res: bool, /// [`syn::Generics`] to search generic parameters in. generics: &'a syn::Generics, } impl<'a> IsVariantGeneric<'a> { /// Constructs a new [`IsVariantGeneric`] [`Visit`]or. fn new(generics: &'a syn::Generics) -> Self { Self { res: false, generics, } } } impl<'ast, 'gen> Visit<'ast> for IsVariantGeneric<'gen> { fn visit_path(&mut self, path: &'ast syn::Path) { if let Some(ident) = path.get_ident() { let is_generic = self.generics.params.iter().any(|par| { if let syn::GenericParam::Type(ty) = par { ty.ident == *ident } else { false } }); if is_generic { self.res = true; } else { syn::visit::visit_path(self, path); } } } }