diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index ebf329cce..cb68ee4db 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -231,8 +231,8 @@ pub trait FromInputValue: Sized { } } -/// Losslessly clones a Rust data type into an InputValue. -pub trait ToInputValue: Sized { +/// Losslessly clones a Rust data type into an [`InputValue`]. +pub trait ToInputValue { /// Performs the conversion. fn to_input_value(&self) -> InputValue; } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 387436ad0..060487793 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,5 +1,5 @@ use crate::{ - GraphQLInputObject, GraphQLScalar, ScalarValue, Value, + GraphQLInputObject, GraphQLScalar, executor::Variables, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, @@ -14,8 +14,8 @@ use crate::{ struct TestComplexScalar; impl TestComplexScalar { - fn to_output(&self) -> Value { - graphql_value!("SerializedValue") + fn to_output(&self) -> &'static str { + "SerializedValue" } fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 1defe431b..b4b0bf5f2 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -8,9 +8,7 @@ //! //! [`BigDecimal`]: bigdecimal::BigDecimal -use std::str::FromStr as _; - -use crate::{Scalar, ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -29,7 +27,8 @@ mod for_minimal_versions_check_only { /// See also [`bigdecimal`] crate for details. /// /// [`bigdecimal`]: https://docs.rs/bigdecimal -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = bigdecimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/bigdecimal", @@ -37,10 +36,11 @@ mod for_minimal_versions_check_only { type BigDecimal = bigdecimal::BigDecimal; mod bigdecimal_scalar { - use super::*; + use super::BigDecimal; + use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &BigDecimal) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &BigDecimal) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(v: &Scalar) -> Result> { @@ -50,13 +50,14 @@ mod bigdecimal_scalar { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); - BigDecimal::from_str(buf.format(f)) + buf.format(f) + .parse::() .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into()) } else { v.try_to::<&str>() .map_err(|e| e.to_string().into()) .and_then(|s| { - BigDecimal::from_str(s).map_err(|e| { + s.parse::().map_err(|e| { format!("Failed to parse `BigDecimal` from `String`: {e}").into() }) }) @@ -66,8 +67,6 @@ mod bigdecimal_scalar { #[cfg(test)] mod test { - use std::str::FromStr as _; - use crate::{FromInputValue as _, InputValue, ToInputValue as _, graphql_input_value}; use super::BigDecimal; @@ -91,7 +90,7 @@ mod test { ] { let input: InputValue = input; let parsed = BigDecimal::from_input_value(&input); - let expected = BigDecimal::from_str(expected).unwrap(); + let expected = expected.parse::().unwrap(); assert!( parsed.is_ok(), @@ -130,7 +129,7 @@ mod test { "123", "43.44", ] { - let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); + let actual: InputValue = raw.parse::().unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 509b3bc12..54b27b518 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -13,7 +13,7 @@ //! [s1]: https://graphql-scalars.dev/docs/scalars/object-id //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `bson` crate. mod for_minimal_versions_check_only { @@ -29,7 +29,8 @@ mod for_minimal_versions_check_only { /// [0]: https://www.mongodb.com/docs/manual/reference/bson-types#objectid /// [1]: https://graphql-scalars.dev/docs/scalars/object-id /// [2]: https://docs.rs/bson/*/bson/oid/struct.ObjectId.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "ObjectID", with = object_id, parse_token(String), @@ -38,10 +39,10 @@ mod for_minimal_versions_check_only { type ObjectId = bson::oid::ObjectId; mod object_id { - use super::*; + use super::ObjectId; - pub(super) fn to_output(v: &ObjectId) -> Value { - Value::scalar(v.to_hex()) + pub(super) fn to_output(v: &ObjectId) -> String { + v.to_hex() } pub(super) fn from_input(s: &str) -> Result> { @@ -62,7 +63,8 @@ mod object_id { /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/bson/*/bson/struct.DateTime.html /// [3]: https://www.mongodb.com/docs/manual/reference/bson-types#date -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -70,13 +72,11 @@ mod object_id { type DateTime = bson::DateTime; mod date_time { - use super::*; + use super::DateTime; - pub(super) fn to_output(v: &DateTime) -> Value { - Value::scalar( - (*v).try_to_rfc3339_string() - .unwrap_or_else(|e| panic!("failed to format `DateTime` as RFC 3339: {e}")), - ) + pub(super) fn to_output(v: &DateTime) -> String { + (*v).try_to_rfc3339_string() + .unwrap_or_else(|e| panic!("failed to format `DateTime` as RFC 3339: {e}")) } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 99e380c8b..ec437c677 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -23,7 +23,7 @@ use std::fmt; use chrono::{FixedOffset, TimeZone}; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -36,7 +36,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -44,18 +45,15 @@ use crate::{ScalarValue, Value, graphql_scalar}; pub type LocalDate = chrono::NaiveDate; mod local_date { - use super::*; + use super::LocalDate; /// Format of a [`LocalDate` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> Value - where - S: ScalarValue, - { - Value::scalar(v.format(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDate) -> String { + v.format(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -75,7 +73,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -85,7 +84,7 @@ pub type LocalTime = chrono::NaiveTime; mod local_time { use chrono::Timelike as _; - use super::*; + use super::LocalTime; /// Full format of a [`LocalTime` scalar][1]. /// @@ -102,18 +101,13 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> Value - where - S: ScalarValue, - { - Value::scalar( - if v.nanosecond() == 0 { - v.format(FORMAT_NO_MILLIS) - } else { - v.format(FORMAT) - } - .to_string(), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + if v.nanosecond() == 0 { + v.format(FORMAT_NO_MILLIS) + } else { + v.format(FORMAT) + } + .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -134,7 +128,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -142,18 +137,15 @@ mod local_time { pub type LocalDateTime = chrono::NaiveDateTime; mod local_date_time { - use super::*; + use super::LocalDateTime; /// Format of a [`LocalDateTime` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.format(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDateTime) -> String { + v.format(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -174,7 +166,8 @@ mod local_date_time { /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -186,20 +179,19 @@ mod local_date_time { pub type DateTime = chrono::DateTime; mod date_time { - use chrono::{SecondsFormat, Utc}; + use std::fmt; + + use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc}; - use super::*; + use super::{DateTime, FromFixedOffset}; - pub(super) fn to_output(v: &DateTime) -> Value + pub(super) fn to_output(v: &DateTime) -> String where - S: ScalarValue, - Tz: chrono::TimeZone, + Tz: TimeZone, Tz::Offset: fmt::Display, { - Value::scalar( - v.with_timezone(&Utc) - .to_rfc3339_opts(SecondsFormat::AutoSi, true), - ) + v.with_timezone(&Utc) + .to_rfc3339_opts(SecondsFormat::AutoSi, true) // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result, Box> diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index ed38c6d87..213414ad6 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -11,7 +11,7 @@ //! [1]: http://www.iana.org/time-zones //! [s1]: https://graphql-scalars.dev/docs/scalars/time-zone -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `chrono-tz` crate. mod for_minimal_versions_check_only { @@ -31,7 +31,8 @@ mod for_minimal_versions_check_only { /// [1]: https://graphql-scalars.dev/docs/scalars/time-zone /// [2]: https://docs.rs/chrono-tz/*/chrono_tz/enum.Tz.html /// [3]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = tz, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", @@ -39,10 +40,10 @@ mod for_minimal_versions_check_only { pub type TimeZone = chrono_tz::Tz; mod tz { - use super::*; + use super::TimeZone; - pub(super) fn to_output(v: &TimeZone) -> Value { - Value::scalar(v.name().to_owned()) + pub(super) fn to_output(v: &TimeZone) -> &'static str { + v.name() } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index 045c95f39..d0e11a786 100644 --- a/juniper/src/integrations/jiff.rs +++ b/juniper/src/integrations/jiff.rs @@ -54,7 +54,7 @@ use std::str; use derive_more::with_trait::{Debug, Display, Error, Into}; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Representation of a civil date in the Gregorian calendar. /// @@ -68,7 +68,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Date.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -83,11 +84,8 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDate) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -107,7 +105,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Time.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -132,18 +131,13 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> Value - where - S: ScalarValue, - { - Value::scalar( - if v.subsec_nanosecond() == 0 { - v.strftime(FORMAT_NO_MILLIS) - } else { - v.strftime(FORMAT) - } - .to_string(), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + if v.subsec_nanosecond() == 0 { + v.strftime(FORMAT_NO_MILLIS) + } else { + v.strftime(FORMAT) + } + .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -170,7 +164,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.DateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -185,11 +180,8 @@ mod local_date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDateTime) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -208,7 +200,8 @@ mod local_date_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/jiff/*/jiff/struct.Timestamp.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -225,11 +218,8 @@ mod date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ"; - pub(super) fn to_output(v: &DateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &DateTime) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -253,7 +243,8 @@ mod date_time { /// [3]: https://docs.rs/jiff/latest/jiff/struct.Timestamp.html /// [4]: https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html /// [5]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = zoned_date_time, parse_token(String), specified_by_url = "https://datatracker.ietf.org/doc/html/rfc9557#section-4.1", @@ -265,11 +256,8 @@ mod zoned_date_time { use super::*; - pub(super) fn to_output(v: &ZonedDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &ZonedDateTime) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -288,7 +276,8 @@ mod zoned_date_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/duration /// [2]: https://docs.rs/jiff/*/jiff/struct.Span.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = duration, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/duration", @@ -300,11 +289,8 @@ mod duration { use super::*; - pub(super) fn to_output(v: &Duration) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Duration) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -326,7 +312,8 @@ mod duration { /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html /// [3]: https://graphql-scalars.dev/docs/scalars/time-zone /// [4]: https://graphql-scalars.dev/docs/scalars/utc-offset -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = time_zone_or_utc_offset, parse_token(String), )] @@ -338,11 +325,8 @@ mod time_zone_or_utc_offset { /// Format of a [`TimeZoneOrUtcOffset`] scalar. const FORMAT: &str = "%:Q"; - pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> Value - where - S: ScalarValue, - { - Value::scalar(v.iana_name().map_or_else( + pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> String { + v.iana_name().map_or_else( || { // If no IANA time zone identifier is available, fall back to displaying the time // offset directly (using format `[+-]HH:MM[:SS]` from RFC 9557, e.g. `+05:30`). @@ -353,7 +337,7 @@ mod time_zone_or_utc_offset { .to_string() }, ToOwned::to_owned, - )) + ) // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -392,7 +376,8 @@ pub enum TimeZoneParsingError { /// [0]: http://iana.org/time-zones /// [1]: https://graphql-scalars.dev/docs/scalars/time-zone /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = time_zone, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", @@ -425,11 +410,8 @@ impl str::FromStr for TimeZone { mod time_zone { use super::*; - pub(super) fn to_output(v: &TimeZone) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &TimeZone) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -446,7 +428,8 @@ mod time_zone { /// /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.Offset.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = utc_offset, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", @@ -469,16 +452,13 @@ mod utc_offset { Ok(offset) } - pub(super) fn to_output(v: &UtcOffset) -> Value - where - S: ScalarValue, - { + pub(super) fn to_output(v: &UtcOffset) -> String { let mut buf = String::new(); let tm = jiff::fmt::strtime::BrokenDownTime::from( &jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(*v)), ); tm.format(FORMAT, &mut buf).unwrap(); - Value::scalar(buf) + buf // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index c8952e47a..16e27dc3d 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -8,9 +8,7 @@ //! //! [`Decimal`]: rust_decimal::Decimal -use std::str::FromStr as _; - -use crate::{Scalar, ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// 128 bit representation of a fixed-precision decimal number. /// @@ -26,7 +24,8 @@ use crate::{Scalar, ScalarValue, Value, graphql_scalar}; /// See also [`rust_decimal`] crate for details. /// /// [`rust_decimal`]: https://docs.rs/rust_decimal -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = rust_decimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/rust_decimal", @@ -34,10 +33,11 @@ use crate::{Scalar, ScalarValue, Value, graphql_scalar}; type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { - use super::*; + use super::Decimal; + use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &Decimal) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Decimal) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(v: &Scalar) -> Result> { @@ -50,7 +50,7 @@ mod rust_decimal_scalar { v.try_to::<&str>() .map_err(|e| e.to_string().into()) .and_then(|s| { - Decimal::from_str(s) + s.parse::() .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}").into()) }) } @@ -59,8 +59,6 @@ mod rust_decimal_scalar { #[cfg(test)] mod test { - use std::str::FromStr as _; - use crate::{FromInputValue as _, InputValue, ToInputValue as _, graphql_input_value}; use super::Decimal; @@ -78,7 +76,7 @@ mod test { ] { let input: InputValue = input; let parsed = Decimal::from_input_value(&input); - let expected = Decimal::from_str(expected).unwrap(); + let expected = expected.parse::().unwrap(); assert!( parsed.is_ok(), @@ -112,7 +110,7 @@ mod test { #[test] fn formats_correctly() { for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { - let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); + let actual: InputValue = raw.parse::().unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index e31ebbfb8..f0c0f9e54 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -27,7 +27,7 @@ use time::{ macros::format_description, }; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -40,7 +40,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/time/*/time/struct.Date.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -55,11 +56,10 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]"); - pub(super) fn to_output(v: &LocalDate) -> Value { - Value::scalar( - v.format(FORMAT) - .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")), - ) + pub(super) fn to_output(v: &LocalDate) -> String { + // TODO: Optimize via `Display`? + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -79,7 +79,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/time/*/time/struct.Time.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -106,15 +107,14 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &[BorrowedFormatItem<'_>] = format_description!("[hour]:[minute]"); - pub(super) fn to_output(v: &LocalTime) -> Value { - Value::scalar( - if v.millisecond() == 0 { - v.format(FORMAT_NO_MILLIS) - } else { - v.format(FORMAT) - } - .unwrap_or_else(|e| panic!("failed to format `LocalTime`: {e}")), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + // TODO: Optimize via `Display`? + if v.millisecond() == 0 { + v.format(FORMAT_NO_MILLIS) + } else { + v.format(FORMAT) + } + .unwrap_or_else(|e| panic!("failed to format `LocalTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -135,7 +135,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -151,11 +152,10 @@ mod local_date_time { const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]"); - pub(super) fn to_output(v: &LocalDateTime) -> Value { - Value::scalar( - v.format(FORMAT) - .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")), - ) + pub(super) fn to_output(v: &LocalDateTime) -> String { + // TODO: Optimize via `Display`? + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -175,7 +175,8 @@ mod local_date_time { /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -185,12 +186,11 @@ pub type DateTime = time::OffsetDateTime; mod date_time { use super::*; - pub(super) fn to_output(v: &DateTime) -> Value { - Value::scalar( - v.to_offset(UtcOffset::UTC) - .format(&Rfc3339) - .unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}")), - ) + pub(super) fn to_output(v: &DateTime) -> String { + // TODO: Optimize via `Display`? + v.to_offset(UtcOffset::UTC) + .format(&Rfc3339) + .unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -215,7 +215,8 @@ const UTC_OFFSET_FORMAT: &[BorrowedFormatItem<'_>] = /// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset /// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = utc_offset, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", @@ -225,11 +226,10 @@ pub type UtcOffset = time::UtcOffset; mod utc_offset { use super::*; - pub(super) fn to_output(v: &UtcOffset) -> Value { - Value::scalar( - v.format(UTC_OFFSET_FORMAT) - .unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}")), - ) + pub(super) fn to_output(v: &UtcOffset) -> String { + // TODO: Optimize via `Display`? + v.format(UTC_OFFSET_FORMAT) + .unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 8bae4896c..412d67152 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -9,7 +9,7 @@ //! [`Url`]: url::Url //! [s1]: https://graphql-scalars.dev/docs/scalars/url -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// [Standard URL][0] format as specified in [RFC 3986]. /// @@ -21,20 +21,18 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// [1]: https://graphql-scalars.dev/docs/scalars/url /// [2]: https://docs.rs/url/*/url/struct.Url.html /// [RFC 3986]: https://datatracker.ietf.org/doc/html/rfc3986 -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "URL", with = url_scalar, + to_output_with = Url::as_str, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/url", )] type Url = url::Url; mod url_scalar { - use super::*; - - pub(super) fn to_output(v: &Url) -> Value { - Value::scalar(v.as_str().to_owned()) - } + use super::Url; pub(super) fn from_input(s: &str) -> Result> { Url::parse(s).map_err(|e| format!("Failed to parse `URL`: {e}").into()) diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 70ce7c7cb..067b6b589 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -9,7 +9,7 @@ //! [`Uuid`]: uuid::Uuid //! [s1]: https://graphql-scalars.dev/docs/scalars/uuid -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// [Universally Unique Identifier][0] (UUID). /// @@ -20,7 +20,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// [0]: https://en.wikipedia.org/wiki/Universally_unique_identifier /// [1]: https://graphql-scalars.dev/docs/scalars/uuid /// [2]: https://docs.rs/uuid/*/uuid/struct.Uuid.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "UUID", with = uuid_scalar, parse_token(String), @@ -29,10 +30,10 @@ use crate::{ScalarValue, Value, graphql_scalar}; type Uuid = uuid::Uuid; mod uuid_scalar { - use super::*; + use super::Uuid; - pub(super) fn to_output(v: &Uuid) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Uuid) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 7741d297f..373ee877b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -104,7 +104,8 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult, - ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, + ParseScalarValue, Scalar, ScalarValue, ToScalarValue, TryToPrimitive, Value, + WrongInputScalarTypeError, }, }; diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 75a414aa2..97bb89081 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -7,7 +7,7 @@ use std::convert::Infallible; use derive_more::with_trait::Display; use futures::future::{self, BoxFuture}; -use crate::{FieldError, InputValue, ScalarValue}; +use crate::{FieldError, InputValue, ScalarValue, ToScalarValue}; /// This trait is used by [`graphql_scalar`] macro to retrieve [`Error`] type from a [`Result`]. /// @@ -94,3 +94,34 @@ impl ToResultCall for &fn(I) -> Result { self(input) } } + +/// [Autoref-based specialized][0] coercion into a [`ScalarValue`] for a function call for providing +/// a return-type polymorphism in macros. +/// +/// [0]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html +pub trait ToScalarValueCall { + /// Input of this function. + type Input; + + /// Calls this function, coercing its output into a [`ScalarValue`]. + fn __to_scalar_value_call(&self, input: Self::Input) -> S; +} + +impl ToScalarValueCall for &fn(I) -> S { + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + self(input) + } +} + +impl ToScalarValueCall for fn(I) -> O +where + O: ToScalarValue, +{ + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + self(input).to_scalar_value() + } +} diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 4ea31b990..58047dd8f 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -90,7 +90,10 @@ impl> FromInputValue for Option { } } -impl> ToInputValue for Option { +impl ToInputValue for Option +where + T: ToInputValue, +{ fn to_input_value(&self) -> InputValue { match self { Some(v) => v.to_input_value(), @@ -176,7 +179,6 @@ impl> FromInputValue for Vec { impl ToInputValue for Vec where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -270,10 +272,9 @@ where } } -impl ToInputValue for &[T] +impl ToInputValue for [T] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -462,7 +463,6 @@ where impl ToInputValue for [T; N] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 3c4b47895..79d399f68 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -302,11 +302,10 @@ impl> FromInputValue for Nullable { impl ToInputValue for Nullable where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { - match *self { - Self::Some(ref v) => v.to_input_value(), + match self { + Self::Some(v) => v.to_input_value(), _ => InputValue::null(), } } diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 1bf66f5a0..a3d496f7a 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -1,4 +1,4 @@ -use std::{fmt, sync::Arc}; +use std::sync::Arc; use arcstr::ArcStr; @@ -11,7 +11,7 @@ use crate::{ async_await::GraphQLValueAsync, base::{Arguments, GraphQLType, GraphQLValue}, }, - value::{FromScalarValue, ScalarValue}, + value::{FromScalarValue, ScalarValue, ToScalarValue}, }; impl GraphQLType for Box @@ -99,6 +99,15 @@ where } } +impl ToScalarValue for Box +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Box where S: ScalarValue, @@ -113,8 +122,7 @@ where impl ToInputValue for Box where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -204,10 +212,18 @@ where } } +impl ToScalarValue for &T +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl ToInputValue for &T where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -299,6 +315,15 @@ where } } +impl ToScalarValue for Arc +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Arc where S: ScalarValue, @@ -313,8 +338,7 @@ where impl ToInputValue for Arc where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 272fca57c..e98d680e1 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -17,7 +17,7 @@ use crate::{ subscriptions::GraphQLSubscriptionValue, }, value::{ - FromScalarValue, ParseScalarResult, ScalarValue, TryToPrimitive, Value, + FromScalarValue, ParseScalarResult, ScalarValue, ToScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, }, }; @@ -35,8 +35,8 @@ use crate::{ pub struct ID(Box); impl ID { - fn to_output(&self) -> Value { - Value::scalar(self.0.clone().into_string()) + fn to_output(&self) -> &str { + &self.0 } fn from_input(v: &Scalar) -> Result> { @@ -59,7 +59,11 @@ impl ID { } #[graphql_scalar] -#[graphql(with = impl_string_scalar, from_input_with = __builtin)] +#[graphql( + with = impl_string_scalar, + to_output_with = String::as_str + from_input_with = __builtin, +)] type String = std::string::String; mod impl_string_scalar { @@ -76,10 +80,6 @@ mod impl_string_scalar { } } - pub(super) fn to_output(v: &str) -> Value { - Value::scalar(v.to_owned()) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); @@ -190,10 +190,10 @@ type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { use super::ArcStr; - use crate::{FromScalarValue, IntoValue as _, Scalar, ScalarValue, Value}; + use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &ArcStr) -> Value { - v.into_value() + pub(super) fn to_output(v: &ArcStr) -> S { + S::from_displayable(v) // TODO: Better ergonomics? } pub(super) fn from_input( @@ -213,10 +213,10 @@ type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { use super::CompactString; - use crate::{FromScalarValue, IntoValue as _, Scalar, ScalarValue, Value}; + use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &CompactString) -> Value { - v.into_value() + pub(super) fn to_output(v: &CompactString) -> S { + S::from_displayable(v) // TODO: Better ergonomics? } pub(super) fn from_input( @@ -291,15 +291,6 @@ where } } -impl ToInputValue for &str -where - S: ScalarValue, -{ - fn to_input_value(&self) -> InputValue { - InputValue::scalar(String::from(*self)) - } -} - impl<'s, S> FromScalarValue<'s, S> for &'s str where S: TryToPrimitive<'s, Self, Error: IntoFieldError> + 's, @@ -311,6 +302,21 @@ where } } +impl ToScalarValue for str { + fn to_scalar_value(&self) -> S { + S::from_displayable(self) + } +} + +impl ToInputValue for str +where + str: ToScalarValue, +{ + fn to_input_value(&self) -> InputValue { + InputValue::Scalar(self.to_scalar_value()) + } +} + #[graphql_scalar] #[graphql(with = impl_boolean_scalar, from_input_with = __builtin)] type Boolean = bool; @@ -329,8 +335,8 @@ mod impl_boolean_scalar { } } - pub(super) fn to_output(v: &Boolean) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Boolean) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -357,8 +363,8 @@ mod impl_int_scalar { } } - pub(super) fn to_output(v: &Int) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Int) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,8 +396,8 @@ mod impl_float_scalar { } } - pub(super) fn to_output(v: &Float) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Float) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e6deed359..ec2b08f80 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -10,7 +10,7 @@ pub use self::{ object::Object, scalar::{ AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar, - ScalarValue, TryToPrimitive, WrongInputScalarTypeError, + ScalarValue, ToScalarValue, TryToPrimitive, WrongInputScalarTypeError, }, }; use crate::{ diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index e0cd173aa..5a3a437ae 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -570,6 +570,12 @@ impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> } } +pub trait ToScalarValue { + /// Converts this value into a [`ScalarValue`]. + #[must_use] + fn to_scalar_value(&self) -> S; +} + /// Transparent wrapper over a value, indicating it being a [`ScalarValue`]. /// /// Used in [`GraphQLScalar`] definitions to distinguish a concrete type for a generic diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index f81c25537..be7ae8a90 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -319,6 +319,7 @@ impl ToTokens for Definition { 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_scalar_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_scalar_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); @@ -402,16 +403,46 @@ impl Definition { fn impl_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let resolve = self.methods.expand_resolve(scalar); + let (ty, mut generics) = self.impl_self_and_generics(false); + + let resolve_body = match &self.methods { + Methods::Custom { .. } + | Methods::Delegated { + to_output: Some(_), .. + } => { + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::core::result::Result::Ok(::juniper::Value::Scalar( + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(self) + )) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::GraphQLValue<#scalar> + }); + + quote! { + ::juniper::GraphQLValue::<#scalar>::resolve( + &self.#field, + info, + selection, + executor, + ) + } + } + }; - 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 - { + impl #impl_gens ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = (); type TypeInfo = (); @@ -428,7 +459,7 @@ impl Definition { selection: ::core::option::Option<&[::juniper::Selection<'_, #scalar>]>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { - #resolve + #resolve_body } } } @@ -447,9 +478,7 @@ impl Definition { quote! { #[automatically_derived] - impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty - #where_clause - { + impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_async<'b>( &'b self, info: &'b Self::TypeInfo, @@ -463,6 +492,53 @@ impl Definition { } } + /// Returns generated code implementing [`ToScalarValue`] trait for this [GraphQL scalar][1]. + /// + /// [`ToScalarValue`]: juniper::ToScalarValue + /// [1]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_to_scalar_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (ty, mut generics) = self.impl_self_and_generics(false); + + let body = match &self.methods { + Methods::Custom { to_output, .. } + | Methods::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { + use ::juniper::macros::helper::ToScalarValueCall as _; + + let func: fn(_) -> _ = #to_output; + (&&func).__to_scalar_value_call(self) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(&self.#field) + } + } + }; + + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::ToScalarValue<#scalar> for #ty #where_clause { + fn to_scalar_value(&self) -> #scalar { + #body + } + } + } + } + /// Returns generated code implementing [`InputValue`] trait for this /// [GraphQL scalar][1]. /// @@ -471,18 +547,43 @@ impl Definition { 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, mut generics) = self.impl_self_and_generics(false); + + let body = match &self.methods { + Methods::Custom { .. } + | Methods::Delegated { + to_output: Some(_), .. + } => { + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::juniper::InputValue::Scalar( + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(self) + ) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::ToInputValue<#scalar> + }); + + quote! { + ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) + } + } + }; - 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 - { + impl #impl_gens ::juniper::ToInputValue<#scalar> for #ty #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - #to_input_value + #body } } } @@ -818,54 +919,6 @@ enum Methods { } 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 [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str