From e9a1527235d25b1b10405abeba14c1b36faccc74 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Jun 2025 16:19:03 +0200 Subject: [PATCH 01/22] Strip `InputValue` from `from_input` for scalars --- juniper/src/executor_tests/variables.rs | 6 +- juniper/src/integrations/bigdecimal.rs | 12 +-- juniper/src/integrations/bson.rs | 14 +-- juniper/src/integrations/chrono.rs | 36 +++---- juniper/src/integrations/chrono_tz.rs | 8 +- juniper/src/integrations/jiff.rs | 83 ++++++---------- juniper/src/integrations/rust_decimal.rs | 12 +-- juniper/src/integrations/time.rs | 32 +++--- juniper/src/integrations/url.rs | 8 +- juniper/src/integrations/uuid.rs | 8 +- juniper/src/types/scalars.rs | 49 +++++----- juniper/src/validation/test_harness.rs | 6 +- juniper_codegen/src/graphql_scalar/mod.rs | 6 +- juniper_codegen/src/lib.rs | 71 ++++++-------- .../scalar/type_alias/attr_invalid_url.rs | 8 +- .../tests/codegen_scalar_attr_derive_input.rs | 88 ++++++++--------- .../tests/codegen_scalar_attr_type_alias.rs | 98 +++++++++---------- .../tests/codegen_scalar_derive.rs | 86 ++++++++-------- tests/integration/tests/custom_scalar.rs | 10 +- 19 files changed, 295 insertions(+), 346 deletions(-) diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index bcfc9b84a..cda442978 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -18,11 +18,11 @@ impl TestComplexScalar { graphql_value!("SerializedValue") } - fn from_input(v: &InputValue) -> Result { - v.as_string_value() + fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() .filter(|s| *s == "SerializedValue") .map(|_| Self) - .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {v}"#)) + .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {s}"#)) } } diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 6c9a39b01..5e9602ed8 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -43,18 +43,18 @@ mod bigdecimal_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { - if let Some(i) = v.as_int_value() { + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + if let Some(i) = s.as_int() { Ok(BigDecimal::from(i)) - } else if let Some(f) = v.as_float_value() { + } else if let Some(f) = s.as_float() { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); BigDecimal::from_str(buf.format(f)) .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}")) } else { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { BigDecimal::from_str(s) .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {e}")) diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 246bb3a27..5d53cfa7b 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; // TODO: Try remove on upgrade of `bson` crate. mod for_minimal_versions_check_only { @@ -44,9 +44,9 @@ mod object_id { Value::scalar(v.to_hex()) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectID`: {e}")) }) @@ -83,9 +83,9 @@ mod date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse_rfc3339_str(s) .map_err(|e| format!("Failed to parse `DateTime`: {e}")) diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 3b089734f..812a0a897 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -58,12 +58,9 @@ mod local_date { Value::scalar(v.format(FORMAT).to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDate::parse_from_str(s, FORMAT) .map_err(|e| format!("Invalid `LocalDate`: {e}")) @@ -124,12 +121,9 @@ mod local_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing @@ -172,12 +166,9 @@ mod local_date_time { Value::scalar(v.format(FORMAT).to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDateTime::parse_from_str(s, FORMAT) .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) @@ -225,13 +216,12 @@ mod date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result, String> + pub(super) fn from_input(s: &impl ScalarValue) -> Result, String> where - S: ScalarValue, Tz: TimeZone + FromFixedOffset, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::::parse_from_rfc3339(s) .map_err(|e| format!("Invalid `DateTime`: {e}")) diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index c6f208fc5..eae7411f5 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; // TODO: Try remove on upgrade of `chrono-tz` crate. mod for_minimal_versions_check_only { @@ -45,9 +45,9 @@ mod tz { Value::scalar(v.name().to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { s.parse::() .map_err(|e| format!("Failed to parse `TimeZone`: {e}")) diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index de452289b..a05cc87ad 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// Representation of a civil date in the Gregorian calendar. /// @@ -90,12 +90,9 @@ mod local_date { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDate::strptime(FORMAT, s).map_err(|e| format!("Invalid `LocalDate`: {e}")) }) @@ -153,12 +150,9 @@ mod local_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing @@ -207,12 +201,9 @@ mod local_date_time { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDateTime::strptime(FORMAT, s) .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) @@ -254,12 +245,9 @@ mod date_time { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}"))) } } @@ -299,12 +287,9 @@ mod zoned_date_time { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { ZonedDateTime::from_str(s).map_err(|e| format!("Invalid `ZonedDateTime`: {e}")) }) @@ -341,12 +326,9 @@ mod duration { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}"))) } } @@ -395,12 +377,9 @@ mod time_zone_or_utc_offset { )) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { TimeZoneOrUtcOffset::get(s) .map_err(TimeZoneParsingError::InvalidTimeZone) @@ -478,12 +457,9 @@ mod time_zone { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| s.parse().map_err(|e| format!("Invalid `TimeZone`: {e}"))) } } @@ -531,12 +507,9 @@ mod utc_offset { Value::scalar(buf) } - pub(super) fn from_input(v: &InputValue) -> Result - where - S: ScalarValue, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| utc_offset_from_str(s).map_err(|e| format!("Invalid `UtcOffset`: {e}"))) } } diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index 50a5370c3..62146e0e0 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// 128 bit representation of a fixed-precision decimal number. /// @@ -40,14 +40,14 @@ mod rust_decimal_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { - if let Some(i) = v.as_int_value() { + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + if let Some(i) = s.as_int() { Ok(Decimal::from(i)) - } else if let Some(f) = v.as_float_value() { + } else if let Some(f) = s.as_float() { Decimal::try_from(f).map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}")) } else { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { Decimal::from_str(s) .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}")) diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 3c649d1d0..be56d61d9 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -27,7 +27,7 @@ use time::{ macros::format_description, }; -use crate::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -62,9 +62,9 @@ mod local_date { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDate::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDate`: {e}")) }) @@ -121,9 +121,9 @@ mod local_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing @@ -167,9 +167,9 @@ mod local_date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}")) }) @@ -206,9 +206,9 @@ mod date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {e}")) }) @@ -248,9 +248,9 @@ mod utc_offset { ) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { UtcOffset::parse(s, UTC_OFFSET_FORMAT) .map_err(|e| format!("Invalid `UtcOffset`: {e}")) diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index dd2008cbe..3411a4018 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// [Standard URL][0] format as specified in [RFC 3986]. /// @@ -36,9 +36,9 @@ mod url_scalar { Value::scalar(v.as_str().to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `URL`: {e}"))) } } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 7657d747f..d8da993a0 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::{InputValue, ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, Value, graphql_scalar}; /// [Universally Unique Identifier][0] (UUID). /// @@ -35,9 +35,9 @@ mod uuid_scalar { 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}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `UUID`: {e}"))) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index c15af3a13..59f5d9dbe 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -34,12 +34,11 @@ impl ID { Value::scalar(self.0.clone()) } - fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .map(str::to_owned) - .or_else(|| v.as_int_value().as_ref().map(ToString::to_string)) + fn from_input(s: &impl ScalarValue) -> Result { + s.as_string() + .or_else(|| s.as_int().as_ref().map(ToString::to_string)) .map(Self) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) } } @@ -61,9 +60,8 @@ mod impl_string_scalar { Value::scalar(v.to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .map(str::to_owned) + pub(super) fn from_input(v: &impl ScalarValue) -> Result { + v.as_string() .ok_or_else(|| format!("Expected `String`, found: {v}")) } @@ -176,7 +174,7 @@ where type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { - use crate::{InputValue, IntoValue as _, ScalarValue, Value}; + use crate::{IntoValue as _, ScalarValue, Value}; use super::ArcStr; @@ -184,10 +182,10 @@ mod impl_arcstr_scalar { v.into_value() } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() .map(Into::into) - .ok_or_else(|| format!("Expected `String`, found: {v}")) + .ok_or_else(|| format!("Expected `String`, found: {s}")) } } @@ -196,7 +194,7 @@ mod impl_arcstr_scalar { type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { - use crate::{InputValue, IntoValue as _, ScalarValue, Value}; + use crate::{IntoValue as _, ScalarValue, Value}; use super::CompactString; @@ -204,10 +202,10 @@ mod impl_compactstring_scalar { v.into_value() } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_string_value() + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_str() .map(Into::into) - .ok_or_else(|| format!("Expected `String`, found: {v}")) + .ok_or_else(|| format!("Expected `String`, found: {s}")) } } @@ -292,10 +290,9 @@ mod impl_boolean_scalar { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_scalar_value() - .and_then(ScalarValue::as_bool) - .ok_or_else(|| format!("Expected `Boolean`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_bool() + .ok_or_else(|| format!("Expected `Boolean`, found: {s}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -315,9 +312,9 @@ mod impl_int_scalar { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .ok_or_else(|| format!("Expected `Int`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_int() + .ok_or_else(|| format!("Expected `Int`, found: {s}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -342,9 +339,9 @@ mod impl_float_scalar { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_float_value() - .ok_or_else(|| format!("Expected `Float`, found: {v}")) + pub(super) fn from_input(s: &impl ScalarValue) -> Result { + s.as_float() + .ok_or_else(|| format!("Expected `Float`, found: {s}")) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { diff --git a/juniper/src/validation/test_harness.rs b/juniper/src/validation/test_harness.rs index 576df2090..dcf765017 100644 --- a/juniper/src/validation/test_harness.rs +++ b/juniper/src/validation/test_harness.rs @@ -233,7 +233,7 @@ where { type Error = &'static str; - fn from_input_value<'a>(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { match v.as_enum_value() { Some("SIT") => Ok(DogCommand::Sit), Some("HEEL") => Ok(DogCommand::Heel), @@ -335,7 +335,7 @@ where { type Error = &'static str; - fn from_input_value<'a>(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { match v.as_enum_value() { Some("BROWN") => Ok(FurColor::Brown), Some("BLACK") => Ok(FurColor::Black), @@ -611,7 +611,7 @@ where { type Error = FieldError; - fn from_input_value<'a>(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { let obj = v.to_object_value().ok_or("Expected object")?; Ok(ComplexInput { diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 4338e1fad..6f42420a7 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -770,7 +770,11 @@ impl Methods { from_input: Some(from_input), .. } => { - quote! { #from_input(input) } + quote! { + let input = ::juniper::InputValue::as_scalar(input) + .ok_or_else(|| format!("Expected GraphQL scalar, found: {input}"))?; + #from_input(input) + } } Self::Delegated { field, .. } => { let field_ty = field.ty(); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 4100f6880..cc9d58736 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -440,20 +440,20 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// Customization of a [GraphQL scalar][0] type parsing is possible via /// `#[graphql(from_input_with = )]` attribute: /// ```rust -/// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; +/// # use juniper::{DefaultScalarValue, GraphQLScalar, 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, +/// /// Checks whether the [`ScalarValue`] is a [`String`] beginning with +/// /// `id: ` and strips it. +/// fn from_input( +/// input: &impl ScalarValue, /// ) -> Result { /// // ^^^^^^ must implement `IntoFieldError` -/// input.as_string_value() +/// input.as_str() /// .ok_or_else(|| format!("Expected `String`, found: {input}")) /// .and_then(|str| { /// str.strip_prefix("id: ") @@ -476,8 +476,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// `#[graphql(parse_token()]` attributes: /// ```rust /// # use juniper::{ -/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -/// # ScalarValue, ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarValue, +/// # ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -501,10 +501,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// 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)) +/// fn from_input(v: &impl ScalarValue) -> Result { +/// v.as_string() +/// .map(StringOrInt::String) +/// .or_else(|| v.as_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -523,8 +523,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// `parse_token()` functions: /// ```rust /// # use juniper::{ -/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -/// # ScalarValue, ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarValue, +/// # ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -544,10 +544,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// 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)) +/// pub(super) fn from_input(v: &impl ScalarValue) -> Result { +/// v.as_string() +/// .map(StringOrInt::String) +/// .or_else(|| v.as_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -563,8 +563,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// A regular `impl` block is also suitable for that: /// ```rust /// # use juniper::{ -/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -/// # ScalarValue, ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarValue, +/// # ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -582,13 +582,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// 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)) +/// fn from_input(v: &impl ScalarValue) -> Result { +/// v.as_string() +/// .map(Self::String) +/// .or_else(|| v.as_int().map(Self::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -607,8 +604,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// At the same time, any custom function still may be specified separately: /// ```rust /// # use juniper::{ -/// # GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, -/// # ScalarToken, Value +/// # GraphQLScalar, ParseScalarResult, ScalarValue, ScalarToken, Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -634,13 +630,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// 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)) +/// pub(super) fn from_input(v: &impl ScalarValue) -> Result { +/// v.as_string() +/// .map(StringOrInt::String) +/// .or_else(|| v.as_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -728,7 +721,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// # } /// # /// # use juniper::DefaultScalarValue as CustomScalarValue; -/// use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; +/// use juniper::{graphql_scalar, ScalarValue, Value}; /// /// #[graphql_scalar] /// #[graphql( @@ -747,8 +740,8 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// Value::scalar(v.to_string()) /// } /// -/// pub(super) fn from_input(v: &InputValue) -> Result { -/// v.as_string_value() +/// pub(super) fn from_input(v: &CustomScalarValue) -> Result { +/// v.as_str() /// .ok_or_else(|| format!("Expected `String`, found: {v}")) /// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) /// } diff --git a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs index 61b9c340e..812f6756e 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs +++ b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs @@ -1,4 +1,6 @@ -use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; +use std::convert::Infallible; + +use juniper::{graphql_scalar, ScalarValue, Value}; struct ScalarSpecifiedByUrl; @@ -16,9 +18,7 @@ mod scalar { Value::scalar(0) } - pub(super) fn from_input( - _: &InputValue, - ) -> Result { + pub(super) fn from_input(_: &impl ScalarValue) -> Result { Ok(ScalarSpecifiedByUrl) } } diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index d5ac7060d..4a2116c4e 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -8,8 +8,8 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, - graphql_object, graphql_scalar, graphql_value, graphql_vars, + ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, graphql_object, + graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ @@ -31,10 +31,10 @@ mod trivial { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -239,10 +239,10 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -314,10 +314,10 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,8 +390,8 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(v: &impl ScalarValue) -> prelude::Result { + v.as_int() .map(Self) .ok_or_else(|| format!("Expected `Counter`, found: {v}")) } @@ -468,11 +468,11 @@ mod multiple_delegated_parse_token { } } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_string_value() - .map(|s| Self::String(s.to_owned())) - .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_string() + .map(Self::String) + .or_else(|| s.as_int().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) } } @@ -531,14 +531,13 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(v: &InputValue) -> prelude::Result, prelude::String> + fn from_input(s: &impl ScalarValue) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) @@ -601,10 +600,10 @@ mod with_self { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -689,16 +688,15 @@ mod with_module { Value::scalar(v.0.to_rfc3339()) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) @@ -763,10 +761,10 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -839,10 +837,10 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -915,10 +913,10 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -991,10 +989,10 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -1066,10 +1064,10 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 25289ab80..09411d702 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -6,8 +6,8 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, - graphql_object, graphql_scalar, graphql_value, graphql_vars, + ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, graphql_object, + graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ @@ -36,10 +36,10 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -117,12 +117,10 @@ mod explicit_name { Value::scalar(v.0) } - fn from_input( - v: &InputValue, - ) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -219,10 +217,10 @@ mod delegated_parse_token { Value::scalar(v.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } struct QueryRoot; @@ -301,13 +299,11 @@ mod multiple_delegated_parse_token { } } - fn from_input( - v: &InputValue, - ) -> prelude::Result { - v.as_string_value() - .map(|s| StringOrInt::String(s.to_owned())) - .or_else(|| v.as_int_value().map(StringOrInt::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_string() + .map(StringOrInt::String) + .or_else(|| s.as_int().map(StringOrInt::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) } struct QueryRoot; @@ -367,14 +363,13 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(v: &InputValue) -> prelude::Result, prelude::String> + fn from_input(s: &impl ScalarValue) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) @@ -439,10 +434,10 @@ mod with_self { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -529,16 +524,15 @@ mod with_module { Value::scalar(v.0.to_rfc3339()) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + s.as_str() + .ok_or_else(|| format!("Expected `String`, found: {s}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) @@ -607,12 +601,12 @@ mod description_from_doc_comment { Value::scalar(v.0) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result { - v.as_int_value() + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -693,12 +687,12 @@ mod description_from_attribute { Value::scalar(v.0) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result { - v.as_int_value() + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -779,12 +773,12 @@ mod custom_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result { - v.as_int_value() + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -865,12 +859,12 @@ mod generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result { - v.as_int_value() + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -951,12 +945,12 @@ mod bounded_generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + s: &impl ScalarValue, ) -> prelude::Result { - v.as_int_value() + s.as_int() .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index c5f78f0a0..6a18ab1f0 100644 --- a/tests/integration/tests/codegen_scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -6,8 +6,8 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, - Value, execute, graphql_object, graphql_value, graphql_vars, + GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, + graphql_object, graphql_value, graphql_vars, }; use self::common::{ @@ -29,10 +29,10 @@ mod trivial { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -237,10 +237,10 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -313,10 +313,10 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,10 +390,10 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -469,11 +469,11 @@ mod multiple_delegated_parse_token { } } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_string_value() - .map(|s| Self::String(s.to_owned())) - .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_string() + .map(Self::String) + .or_else(|| s.as_int().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) } } @@ -533,13 +533,12 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(v: &InputValue) -> prelude::Result, prelude::String> + fn from_input(v: &impl ScalarValue) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() + v.as_str() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) @@ -604,10 +603,10 @@ mod with_self { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -693,15 +692,14 @@ mod with_module { Value::scalar(v.0.to_rfc3339()) } - pub(super) fn from_input( - v: &InputValue, + pub(super) fn from_input( + v: &impl ScalarValue, ) -> prelude::Result, prelude::String> where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() + v.as_str() .ok_or_else(|| format!("Expected `String`, found: {v}")) .and_then(|s| { DateTime::parse_from_rfc3339(s) @@ -768,10 +766,10 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -845,10 +843,10 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -922,10 +920,10 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -999,10 +997,10 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + .ok_or_else(|| format!("Expected `Counter`, found: {s}")) } } @@ -1075,10 +1073,10 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(v: &InputValue) -> prelude::Result { - v.as_int_value() + fn from_input(s: &impl ScalarValue) -> prelude::Result { + s.as_int() .map(Self) - .ok_or_else(|| format!("Expected `String`, found: {v}")) + .ok_or_else(|| format!("Expected `String`, found: {s}")) } } diff --git a/tests/integration/tests/custom_scalar.rs b/tests/integration/tests/custom_scalar.rs index da6f0d127..1107a674e 100644 --- a/tests/integration/tests/custom_scalar.rs +++ b/tests/integration/tests/custom_scalar.rs @@ -22,10 +22,12 @@ mod long { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_scalar_value::() - .copied() - .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {v}")) + pub(super) fn from_input(s: &MyScalarValue) -> Result { + if let MyScalarValue::Long(i) = s { + Ok(*i) + } else { + Err(format!("Expected `MyScalarValue::Long`, found: {s}")) + } } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { From e65ca4ee7ac8ca2b31daf24803c9da2b9d1b90a8 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Jun 2025 20:33:36 +0200 Subject: [PATCH 02/22] Bootstrap `ScalarValue` input polymorphism --- juniper/src/ast.rs | 8 +- juniper/src/executor/mod.rs | 6 ++ juniper/src/lib.rs | 2 +- juniper/src/value/mod.rs | 9 +- juniper/src/value/scalar.rs | 99 ++++++++++++++----- juniper_codegen/src/scalar_value/mod.rs | 126 ++++++++++++++++++------ 6 files changed, 184 insertions(+), 66 deletions(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index dc92cfcff..ce521979c 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -8,7 +8,7 @@ use indexmap::IndexMap; use crate::{ executor::Variables, parser::Spanning, - value::{DefaultScalarValue, ScalarValue}, + value::{DefaultScalarValue, ScalarValue, ScalarValueFmt}, }; /// Type literal in a syntax tree. @@ -474,11 +474,7 @@ impl fmt::Display for InputValue { match self { Self::Null => write!(f, "null"), Self::Scalar(s) => { - if let Some(s) = s.as_str() { - write!(f, "\"{s}\"") - } else { - write!(f, "{s}") - } + fmt::Display::fmt(&ScalarValueFmt(s), f) } Self::Enum(v) => write!(f, "{v}"), Self::Variable(v) => write!(f, "${v}"), diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index cc02440a3..c14a0db86 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -282,6 +282,12 @@ impl IntoFieldError for Cow<'_, str> { } } +impl IntoFieldError for Box { + fn into_field_error(self) -> FieldError { + FieldError::::from(self) + } +} + #[doc(hidden)] pub trait IntoResolvable<'a, S, T, C> where diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index c01c61cb5..8648bdd94 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue, - ScalarValue, Value, + ScalarValue, Value, TryScalarValueTo, ScalarValueFmt, WrongInputScalarTypeError, }, }; diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 08259f60b..385a0254e 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -10,10 +10,9 @@ use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, }; - pub use self::{ object::Object, - scalar::{AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue}, + scalar::{AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue, TryScalarValueTo, ScalarValueFmt, WrongInputScalarTypeError}, }; /// Serializable value returned from query and field execution. @@ -195,11 +194,7 @@ impl fmt::Display for Value { match self { Self::Null => write!(f, "null"), Self::Scalar(s) => { - if let Some(string) = s.as_string() { - write!(f, "\"{string}\"") - } else { - write!(f, "{s}") - } + fmt::Display::fmt(&ScalarValueFmt(s), f) } Self::List(list) => { write!(f, "[")?; diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index c3b6a8c32..098e186ff 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -1,15 +1,17 @@ +use std::convert::Infallible; + use std::{ any::{Any, TypeId}, borrow::Cow, fmt, ptr, }; - -use derive_more::with_trait::From; +use arcstr::ArcStr; +use derive_more::with_trait::{Display, Error, From}; use serde::{Serialize, de::DeserializeOwned}; -use crate::parser::{ParseError, ScalarToken}; +use crate::{InputValue, FieldError, parser::{ParseError, ScalarToken}, IntoFieldError}; #[cfg(doc)] -use crate::{InputValue, Value}; +use crate::{Value, GraphQLValue}; pub use juniper_codegen::ScalarValue; @@ -164,6 +166,11 @@ pub trait ScalarValue: + From + From + From + + for<'a> TryScalarValueTo<'a, bool, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, i32, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, f64, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, String, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, &'a str, Error: IntoFieldError> + 'static { /// Checks whether this [`ScalarValue`] contains the value of the given @@ -191,19 +198,19 @@ pub trait ScalarValue: /// This function is used for implementing [`GraphQLValue`] for [`i32`] for /// all possible [`ScalarValue`]s. Implementations should convert all the /// supported integer types with 32 bit or less to an integer, if requested. - /// - /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] - fn as_int(&self) -> Option; + fn as_int(&self) -> Option { + self.try_scalar_value_to().ok() + } /// Represents this [`ScalarValue`] as a [`String`] value. /// /// This function is used for implementing [`GraphQLValue`] for [`String`] /// for all possible [`ScalarValue`]s. - /// - /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] - fn as_string(&self) -> Option; + fn as_string(&self) -> Option { + self.try_scalar_value_to().ok() + } /// Converts this [`ScalarValue`] into a [`String`] value. /// @@ -216,10 +223,10 @@ pub trait ScalarValue: /// /// This function is used for implementing [`GraphQLValue`] for [`str`] for /// all possible [`ScalarValue`]s. - /// - /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] - fn as_str(&self) -> Option<&str>; + fn as_str(&self) -> Option<&str> { + self.try_scalar_value_to().ok() + } /// Represents this [`ScalarValue`] as a float value. /// @@ -227,18 +234,19 @@ pub trait ScalarValue: /// all possible [`ScalarValue`]s. Implementations should convert all /// supported integer types with 64 bit or less and all floating point /// values with 64 bit or less to a float, if requested. - /// - /// [`GraphQLValue`]: crate::GraphQLValue #[must_use] - fn as_float(&self) -> Option; + fn as_float(&self) -> Option { + self.try_scalar_value_to().ok() + } /// Represents this [`ScalarValue`] as a boolean value /// /// This function is used for implementing [`GraphQLValue`] for [`bool`] for /// all possible [`ScalarValue`]s. - /// - /// [`GraphQLValue`]: crate::GraphQLValue - fn as_bool(&self) -> Option; + #[must_use] + fn as_bool(&self) -> Option { + self.try_scalar_value_to().ok() + } /// Converts this [`ScalarValue`] into another one. fn into_another(self) -> S { @@ -271,6 +279,49 @@ pub trait ScalarValue: } } +pub trait TryScalarValueTo<'me, T: 'me> { + type Error; + + fn try_scalar_value_to(&'me self) -> Result; +} + +impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me S> for S { + type Error = Infallible; + + fn try_scalar_value_to(&'me self) -> Result<&'me S, Self::Error> { + Ok(self) + } +} + +/// Error of a [`ScalarValue`] not matching the expected type. +#[derive(Clone, Debug, Display, Error)] +#[display("Expected `{type_name}` scalar, found: {}", ScalarValueFmt(*input))] +pub struct WrongInputScalarTypeError<'a, S: ScalarValue> { + /// Type name of the expected GraphQL scalar. + pub type_name: ArcStr, + + /// Input [`ScalarValue`] not matching the expected type. + pub input: &'a S, +} + +impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> { + fn into_field_error(self) -> FieldError { + FieldError::::from(self) + } +} + +pub struct ScalarValueFmt<'a, S: ScalarValue>(pub &'a S); + +impl<'a, S: ScalarValue> Display for ScalarValueFmt<'a, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(s) = self.0.as_str() { + write!(f, "\"{s}\"") + } else { + Display::fmt(&self.0, f) + } + } +} + /// Extension of [`Any`] for using its methods directly on the value without `dyn`. pub trait AnyExt: Any { /// Returns `true` if the this type is the same as `T`. @@ -302,13 +353,13 @@ impl AnyExt for T {} /// These types closely follow the [GraphQL specification][0]. /// /// [0]: https://spec.graphql.org/October2021 -#[derive(Clone, Debug, From, PartialEq, ScalarValue, Serialize)] +#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value. /// /// [0]: https://spec.graphql.org/October2021#sec-Int - #[from(ignore)] + #[from(i32)] #[value(as_float, as_int)] Int(i32), @@ -317,7 +368,7 @@ pub enum DefaultScalarValue { /// /// [0]: https://spec.graphql.org/October2021#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point - #[from(ignore)] + #[from(f64)] #[value(as_float)] Float(f64), @@ -325,14 +376,14 @@ pub enum DefaultScalarValue { /// sequences. /// /// [0]: https://spec.graphql.org/October2021#sec-String - #[from(&str, Cow<'_, str>)] + #[from(&str, Cow<'_, str>, String)] #[value(as_str, as_string, into_string)] String(String), /// [`Boolean` scalar][0] as a `true` or `false` value. /// /// [0]: https://spec.graphql.org/October2021#sec-Boolean - #[from(ignore)] + #[from(bool)] #[value(as_bool)] Boolean(bool), } diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index 4af754262..3331496d4 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -247,60 +247,108 @@ struct Definition { 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); + //self.impl_from_tokens().to_tokens(into); + //self.impl_display_tokens().to_tokens(into); } } impl Definition { /// Returns generated code implementing `ScalarValue`. fn impl_scalar_value_tokens(&self) -> TokenStream { - let ident = &self.ident; + let ty_ident = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + let ref_lt = quote! { '___a }; + // 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! { #ref_lt }); + let (lt_impl_gens, _, _) = generics.split_for_impl(); + let methods = [ + ( + Method::IntoString, + quote! { fn into_string(self) -> ::core::option::Option<::std::string::String> }, + quote! { ::std::string::String::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, + } + } + } + }); + + let methods2 = [ ( Method::AsInt, - quote! { fn as_int(&self) -> ::core::option::Option<::core::primitive::i32> }, - quote! { ::core::primitive::i32::from(*v) }, + "Int", + quote! { ::core::primitive::i32 }, + quote! { ::core::convert::Into::into(*v) }, ), ( Method::AsFloat, - quote! { fn as_float(&self) -> ::core::option::Option<::core::primitive::f64> }, - quote! { ::core::primitive::f64::from(*v) }, + "Float", + quote! { ::core::primitive::f64 }, + quote! { ::core::convert::Into::into(*v) }, ), ( Method::AsStr, - quote! { fn as_str(&self) -> ::core::option::Option<&::core::primitive::str> }, + "String", + quote! { &#ref_lt ::core::primitive::str }, quote! { ::core::convert::AsRef::as_ref(v) }, ), ( Method::AsString, - quote! { fn as_string(&self) -> ::core::option::Option<::std::string::String> }, + "String", + quote! { ::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) }, + "Bool", + quote! { ::core::primitive::bool }, + quote! { ::core::convert::Into::into(*v) }, ), ]; - let methods = methods.iter().map(|(m, sig, def)| { + let impls = methods2.iter().map(|(m, into_name, as_ty, default_expr)| { 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), } + let arm_pattern = v.match_arm(); + let call = if let Some(func) = &v.expr { + quote! { #func(v) } + } else { + default_expr.clone() + }; + quote! { + #arm_pattern => ::core::result::Result::Ok(#call), + } }); quote! { - #sig { - match self { - #(#arms)* - _ => ::core::option::Option::None, + #[automatically_derived] + impl #lt_impl_gens ::juniper::TryScalarValueTo<#ref_lt, #as_ty> + for #ty_ident #ty_gens #where_clause + { + type Error = ::juniper::WrongInputScalarTypeError<#ref_lt, #ty_ident #ty_gens>; + + fn try_scalar_value_to( + &#ref_lt self, + ) -> ::core::result::Result<#as_ty, Self::Error> { + match self { + #( #arms )* + _ => ::core::result::Result::Err(::juniper::WrongInputScalarTypeError { + type_name: ::juniper::arcstr::literal!(#into_name), + input: self, + }), + } } } } @@ -318,12 +366,12 @@ impl Definition { quote! { #[automatically_derived] - impl #impl_gens ::juniper::ScalarValue for #ident #ty_gens - #where_clause - { + impl #impl_gens ::juniper::ScalarValue for #ty_ident #ty_gens #where_clause { #( #methods )* #from_displayable } + + #( #impls )* } } @@ -345,6 +393,7 @@ impl Definition { .iter() .map(|v| { let var_ident = &v.ident; + //let var_name = var_ident.to_string(); let field = v.fields.iter().next().unwrap(); let var_ty = &field.ty; let var_field = field @@ -353,6 +402,7 @@ impl Definition { .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 @@ -360,7 +410,7 @@ impl Definition { fn from(v: #var_ty) -> Self { Self::#var_ident #var_field } - } + }*/ #[automatically_derived] impl #impl_gen ::core::convert::From<#ty_ident #ty_gen> @@ -387,6 +437,26 @@ impl Definition { } } } +/* + #[automatically_derived] + impl #lf_impl_gen ::juniper::TryFromScalarValue<&'___a #ty_ident #ty_gen> + for &'___a #var_ty #where_clause + { + type Error = ::juniper::WrongInputScalarTypeError<'___a, #ty_ident #ty_gen>; + + fn try_from_scalar_value( + __value: &'___a #ty_ident #ty_gen + ) -> ::core::result::Result { + if let #ty_ident::#var_ident #var_field = __value { + ::core::result::Result::Ok(v) + } else { + ::core::result::Result::Err(::juniper::WrongInputScalarTypeError { + type_name: ::juniper::arcstr::literal!(#var_name), + input: __value, + }) + } + } + }*/ } }) .collect() From 981f3c5dac498ddfbcc11ddbff6c0ba054654c16 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Jun 2025 21:22:09 +0200 Subject: [PATCH 03/22] Apply `ScalarValue` input polymorphism, vol.1 [skip ci] --- juniper/src/integrations/bson.rs | 18 ++--- juniper/src/integrations/chrono.rs | 51 +++++--------- juniper/src/integrations/chrono_tz.rs | 10 +-- juniper/src/integrations/jiff.rs | 83 ++++++++--------------- juniper/src/integrations/time.rs | 54 +++++---------- juniper/src/integrations/url.rs | 6 +- juniper/src/integrations/uuid.rs | 6 +- juniper/src/types/scalars.rs | 40 +++++------ juniper/src/value/scalar.rs | 3 +- juniper_codegen/src/graphql_scalar/mod.rs | 2 + 10 files changed, 94 insertions(+), 179 deletions(-) diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 5d53cfa7b..509b3bc12 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -44,12 +44,8 @@ mod object_id { Value::scalar(v.to_hex()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectID`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectID`: {e}").into()) } } @@ -83,13 +79,9 @@ mod date_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse_rfc3339_str(s) - .map_err(|e| format!("Failed to parse `DateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + DateTime::parse_rfc3339_str(s) + .map_err(|e| format!("Failed to parse `DateTime`: {e}").into()) } } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 812a0a897..809e4b804 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -58,13 +58,9 @@ mod local_date { Value::scalar(v.format(FORMAT).to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDate::parse_from_str(s, FORMAT) - .map_err(|e| format!("Invalid `LocalDate`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDate::parse_from_str(s, FORMAT) + .map_err(|e| format!("Invalid `LocalDate`: {e}").into()) } } @@ -121,18 +117,13 @@ mod local_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - // First, try to parse the most used format. - // At the end, try to parse the full format for the parsing - // error to be most informative. - LocalTime::parse_from_str(s, FORMAT_NO_MILLIS) - .or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS)) - .or_else(|_| LocalTime::parse_from_str(s, FORMAT)) - .map_err(|e| format!("Invalid `LocalTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + // First, try to parse the most used format. + // At the end, try to parse the full format for the parsing error to be most informative. + LocalTime::parse_from_str(s, FORMAT_NO_MILLIS) + .or_else(|_| LocalTime::parse_from_str(s, FORMAT_NO_SECS)) + .or_else(|_| LocalTime::parse_from_str(s, FORMAT)) + .map_err(|e| format!("Invalid `LocalTime`: {e}").into()) } } @@ -166,13 +157,9 @@ mod local_date_time { Value::scalar(v.format(FORMAT).to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDateTime::parse_from_str(s, FORMAT) - .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDateTime::parse_from_str(s, FORMAT) + .map_err(|e| format!("Invalid `LocalDateTime`: {e}").into()) } } @@ -216,17 +203,13 @@ mod date_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result, String> + pub(super) fn from_input(s: &str) -> Result, Box> where Tz: TimeZone + FromFixedOffset, { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::::parse_from_rfc3339(s) - .map_err(|e| format!("Invalid `DateTime`: {e}")) - .map(FromFixedOffset::from_fixed_offset) - }) + DateTime::::parse_from_rfc3339(s) + .map(FromFixedOffset::from_fixed_offset) + .map_err(|e| format!("Invalid `DateTime`: {e}").into()) } } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index eae7411f5..ed38c6d87 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -45,13 +45,9 @@ mod tz { Value::scalar(v.name().to_owned()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - s.parse::() - .map_err(|e| format!("Failed to parse `TimeZone`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + s.parse::() + .map_err(|e| format!("Failed to parse `TimeZone`: {e}").into()) } } diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index a05cc87ad..41365797b 100644 --- a/juniper/src/integrations/jiff.rs +++ b/juniper/src/integrations/jiff.rs @@ -90,12 +90,8 @@ mod local_date { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDate::strptime(FORMAT, s).map_err(|e| format!("Invalid `LocalDate`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDate::strptime(FORMAT, s).map_err(|e| format!("Invalid `LocalDate`: {e}").into()) } } @@ -150,18 +146,13 @@ mod local_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - // First, try to parse the most used format. - // At the end, try to parse the full format for the parsing - // error to be most informative. - LocalTime::strptime(FORMAT_NO_MILLIS, s) - .or_else(|_| LocalTime::strptime(FORMAT_NO_SECS, s)) - .or_else(|_| LocalTime::strptime(FORMAT, s)) - .map_err(|e| format!("Invalid `LocalTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + // First, try to parse the most used format. + // At the end, try to parse the full format for the parsing error to be most informative. + LocalTime::strptime(FORMAT_NO_MILLIS, s) + .or_else(|_| LocalTime::strptime(FORMAT_NO_SECS, s)) + .or_else(|_| LocalTime::strptime(FORMAT, s)) + .map_err(|e| format!("Invalid `LocalTime`: {e}").into()) } } @@ -201,13 +192,9 @@ mod local_date_time { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDateTime::strptime(FORMAT, s) - .map_err(|e| format!("Invalid `LocalDateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDateTime::strptime(FORMAT, s) + .map_err(|e| format!("Invalid `LocalDateTime`: {e}").into()) } } @@ -245,10 +232,8 @@ mod date_time { Value::scalar(v.strftime(FORMAT).to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}"))) + pub(super) fn from_input(s: &str) -> Result> { + DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}").into()) } } @@ -287,12 +272,8 @@ mod zoned_date_time { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - ZonedDateTime::from_str(s).map_err(|e| format!("Invalid `ZonedDateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + ZonedDateTime::from_str(s).map_err(|e| format!("Invalid `ZonedDateTime`: {e}").into()) } } @@ -326,10 +307,8 @@ mod duration { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}"))) + pub(super) fn from_input(s: &str) -> Result> { + Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}").into()) } } @@ -377,15 +356,11 @@ mod time_zone_or_utc_offset { )) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - TimeZoneOrUtcOffset::get(s) - .map_err(TimeZoneParsingError::InvalidTimeZone) - .or_else(|_| utc_offset::utc_offset_from_str(s).map(TimeZoneOrUtcOffset::fixed)) - .map_err(|e| format!("Invalid `TimeZoneOrUtcOffset`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + TimeZoneOrUtcOffset::get(s) + .map_err(TimeZoneParsingError::InvalidTimeZone) + .or_else(|_| utc_offset::utc_offset_from_str(s).map(TimeZoneOrUtcOffset::fixed)) + .map_err(|e| format!("Invalid `TimeZoneOrUtcOffset`: {e}").into()) } } @@ -457,10 +432,8 @@ mod time_zone { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| s.parse().map_err(|e| format!("Invalid `TimeZone`: {e}"))) + pub(super) fn from_input(s: &str) -> Result> { + s.parse().map_err(|e| format!("Invalid `TimeZone`: {e}").into()) } } @@ -507,10 +480,8 @@ mod utc_offset { Value::scalar(buf) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| utc_offset_from_str(s).map_err(|e| format!("Invalid `UtcOffset`: {e}"))) + pub(super) fn from_input(s: &str) -> Result> { + utc_offset_from_str(s).map_err(|e| format!("Invalid `UtcOffset`: {e}").into()) } } diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index be56d61d9..e31ebbfb8 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -62,12 +62,8 @@ mod local_date { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDate::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDate`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDate::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDate`: {e}").into()) } } @@ -121,18 +117,13 @@ mod local_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - // First, try to parse the most used format. - // At the end, try to parse the full format for the parsing - // error to be most informative. - LocalTime::parse(s, FORMAT_NO_MILLIS) - .or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS)) - .or_else(|_| LocalTime::parse(s, FORMAT)) - .map_err(|e| format!("Invalid `LocalTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + // First, try to parse the most used format. + // At the end, try to parse the full format for the parsing error to be most informative. + LocalTime::parse(s, FORMAT_NO_MILLIS) + .or_else(|_| LocalTime::parse(s, FORMAT_NO_SECS)) + .or_else(|_| LocalTime::parse(s, FORMAT)) + .map_err(|e| format!("Invalid `LocalTime`: {e}").into()) } } @@ -167,12 +158,8 @@ mod local_date_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + LocalDateTime::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDateTime`: {e}").into()) } } @@ -206,13 +193,10 @@ mod date_time { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + DateTime::parse(s, &Rfc3339) .map(|dt| dt.to_offset(UtcOffset::UTC)) + .map_err(|e| format!("Invalid `DateTime`: {e}").into()) } } @@ -248,13 +232,9 @@ mod utc_offset { ) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - UtcOffset::parse(s, UTC_OFFSET_FORMAT) - .map_err(|e| format!("Invalid `UtcOffset`: {e}")) - }) + pub(super) fn from_input(s: &str) -> Result> { + UtcOffset::parse(s, UTC_OFFSET_FORMAT) + .map_err(|e| format!("Invalid `UtcOffset`: {e}").into()) } } diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 3411a4018..8bae4896c 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -36,10 +36,8 @@ mod url_scalar { Value::scalar(v.as_str().to_owned()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `URL`: {e}"))) + 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 d8da993a0..70ce7c7cb 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -35,10 +35,8 @@ mod uuid_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `UUID`: {e}"))) + pub(super) fn from_input(s: &str) -> Result> { + Uuid::parse_str(s).map_err(|e| format!("Failed to parse `UUID`: {e}").into()) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 59f5d9dbe..682fc57be 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -1,4 +1,4 @@ -use std::{char, marker::PhantomData, rc::Rc, thread::JoinHandle}; +use std::{char, marker::PhantomData, rc::Rc, thread::JoinHandle, convert::Infallible}; use derive_more::with_trait::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; @@ -60,9 +60,8 @@ mod impl_string_scalar { Value::scalar(v.to_owned()) } - pub(super) fn from_input(v: &impl ScalarValue) -> Result { - v.as_string() - .ok_or_else(|| format!("Expected `String`, found: {v}")) + pub(super) fn from_input(s: String) -> Result { + Ok(s) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -174,18 +173,17 @@ where type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { - use crate::{IntoValue as _, ScalarValue, Value}; + use std::convert::Infallible; + use crate::{IntoValue as _, ScalarValue, Value}; use super::ArcStr; pub(super) fn to_output(v: &ArcStr) -> Value { v.into_value() } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .map(Into::into) - .ok_or_else(|| format!("Expected `String`, found: {s}")) + pub(super) fn from_input(s: &str) -> Result { + Ok(s.into()) } } @@ -194,18 +192,17 @@ mod impl_arcstr_scalar { type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { - use crate::{IntoValue as _, ScalarValue, Value}; + use std::convert::Infallible; + use crate::{IntoValue as _, ScalarValue, Value}; use super::CompactString; pub(super) fn to_output(v: &CompactString) -> Value { v.into_value() } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() - .map(Into::into) - .ok_or_else(|| format!("Expected `String`, found: {s}")) + pub(super) fn from_input(s: &str) -> Result { + Ok(s.into()) } } @@ -290,9 +287,8 @@ mod impl_boolean_scalar { Value::scalar(*v) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_bool() - .ok_or_else(|| format!("Expected `Boolean`, found: {s}")) + pub(super) fn from_input(b: Boolean) -> Result { + Ok(b) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -312,9 +308,8 @@ mod impl_int_scalar { Value::scalar(*v) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_int() - .ok_or_else(|| format!("Expected `Int`, found: {s}")) + pub(super) fn from_input(i: Int) -> Result { + Ok(i) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -339,9 +334,8 @@ mod impl_float_scalar { Value::scalar(*v) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - s.as_float() - .ok_or_else(|| format!("Expected `Float`, found: {s}")) + pub(super) fn from_input(f: Float) -> Result { + Ok(f) } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 098e186ff..e81e52aa5 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -9,7 +9,7 @@ use arcstr::ArcStr; use derive_more::with_trait::{Display, Error, From}; use serde::{Serialize, de::DeserializeOwned}; -use crate::{InputValue, FieldError, parser::{ParseError, ScalarToken}, IntoFieldError}; +use crate::{FieldError, parser::{ParseError, ScalarToken}, IntoFieldError}; #[cfg(doc)] use crate::{Value, GraphQLValue}; @@ -171,6 +171,7 @@ pub trait ScalarValue: + for<'a> TryScalarValueTo<'a, f64, Error: IntoFieldError> + for<'a> TryScalarValueTo<'a, String, Error: IntoFieldError> + for<'a> TryScalarValueTo<'a, &'a str, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, &'a Self, Error: IntoFieldError> + 'static { /// Checks whether this [`ScalarValue`] contains the value of the given diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 6f42420a7..5718edee7 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -773,6 +773,8 @@ impl Methods { quote! { let input = ::juniper::InputValue::as_scalar(input) .ok_or_else(|| format!("Expected GraphQL scalar, found: {input}"))?; + let input = ::juniper::TryScalarValueTo::try_scalar_value_to(input) + .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error)?; #from_input(input) } } From 441ba9732fd037dd5379826ac0677528082733ef Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Jun 2025 21:50:22 +0200 Subject: [PATCH 04/22] Apply `ScalarValue` input polymorphism, vol.2 [skip ci] --- juniper/Cargo.toml | 1 + juniper/src/ast.rs | 4 +-- juniper/src/integrations/bigdecimal.rs | 26 +++++++++++------- juniper/src/integrations/chrono.rs | 3 +-- juniper/src/integrations/jiff.rs | 3 ++- juniper/src/integrations/rust_decimal.rs | 25 +++++++++++------ juniper/src/lib.rs | 10 ++++--- juniper/src/types/scalars.rs | 34 ++++++++++++++---------- juniper/src/value/mod.rs | 15 ++++++----- juniper/src/value/scalar.rs | 24 ++++++++++++----- juniper_codegen/src/scalar_value/mod.rs | 20 +++++++------- 11 files changed, 101 insertions(+), 64 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 552076d75..cf2dd364e 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -59,6 +59,7 @@ indexmap = { version = "2.0", features = ["serde"] } itertools = "0.14" jiff = { version = "0.2", features = ["std"], default-features = false, optional = true } juniper_codegen = { version = "0.16.0", path = "../juniper_codegen" } +ref-cast = "1.0" rust_decimal = { version = "1.20", default-features = false, optional = true } ryu = { version = "1.0", optional = true } serde = { version = "1.0.122", features = ["derive"] } diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index ce521979c..6e8991295 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -473,9 +473,7 @@ impl fmt::Display for InputValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Null => write!(f, "null"), - Self::Scalar(s) => { - fmt::Display::fmt(&ScalarValueFmt(s), f) - } + Self::Scalar(s) => fmt::Display::fmt(&ScalarValueFmt(s), f), Self::Enum(v) => write!(f, "{v}"), Self::Variable(v) => write!(f, "${v}"), Self::List(v) => { diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 5e9602ed8..cffff7945 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::{Raw, ScalarValue, Value, WrongInputScalarTypeError, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -43,21 +43,29 @@ mod bigdecimal_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - if let Some(i) = s.as_int() { + pub(super) fn from_input(v: &Raw) -> Result> { + if let Some(i) = v.as_int() { Ok(BigDecimal::from(i)) - } else if let Some(f) = s.as_float() { + } else if let Some(f) = v.as_float() { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); BigDecimal::from_str(buf.format(f)) - .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}")) + .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into()) } else { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) + v.as_str() + .ok_or_else(|| { + WrongInputScalarTypeError { + type_name: arcstr::literal!("String"), + input: &**v, + } + .to_string() + .into() + }) .and_then(|s| { - BigDecimal::from_str(s) - .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {e}")) + BigDecimal::from_str(s).map_err(|e| { + format!("Failed to parse `BigDecimal` from `String`: {e}").into() + }) }) } } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 809e4b804..99e380c8b 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -59,8 +59,7 @@ mod local_date { } pub(super) fn from_input(s: &str) -> Result> { - LocalDate::parse_from_str(s, FORMAT) - .map_err(|e| format!("Invalid `LocalDate`: {e}").into()) + LocalDate::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `LocalDate`: {e}").into()) } } diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index 41365797b..045c95f39 100644 --- a/juniper/src/integrations/jiff.rs +++ b/juniper/src/integrations/jiff.rs @@ -433,7 +433,8 @@ mod time_zone { } pub(super) fn from_input(s: &str) -> Result> { - s.parse().map_err(|e| format!("Invalid `TimeZone`: {e}").into()) + s.parse() + .map_err(|e| format!("Invalid `TimeZone`: {e}").into()) } } diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index 62146e0e0..beafcb7b5 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::{Raw, ScalarValue, Value, graphql_scalar}; /// 128 bit representation of a fixed-precision decimal number. /// @@ -35,22 +35,31 @@ type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { use super::*; + use crate::WrongInputScalarTypeError; pub(super) fn to_output(v: &Decimal) -> Value { Value::scalar(v.to_string()) } - pub(super) fn from_input(s: &impl ScalarValue) -> Result { - if let Some(i) = s.as_int() { + pub(super) fn from_input(v: &Raw) -> Result> { + if let Some(i) = v.as_int() { Ok(Decimal::from(i)) - } else if let Some(f) = s.as_float() { - Decimal::try_from(f).map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}")) + } else if let Some(f) = v.as_float() { + Decimal::try_from(f) + .map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}").into()) } else { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) + v.as_str() + .ok_or_else(|| { + WrongInputScalarTypeError { + type_name: arcstr::literal!("String"), + input: &**v, + } + .to_string() + .into() + }) .and_then(|s| { Decimal::from_str(s) - .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}")) + .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}").into()) }) } } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 8648bdd94..ac800a2f7 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -56,9 +56,9 @@ pub mod tests; #[cfg(test)] mod executor_tests; -use derive_more::with_trait::{Display, From}; +use derive_more::with_trait::{Deref, Display, From}; use itertools::Itertools as _; - +use ref_cast::RefCast; // Needs to be public because macros use it. pub use crate::util::to_camel_case; @@ -104,7 +104,7 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue, - ScalarValue, Value, TryScalarValueTo, ScalarValueFmt, WrongInputScalarTypeError, + ScalarValue, ScalarValueFmt, TryScalarValueTo, Value, WrongInputScalarTypeError, }, }; @@ -319,3 +319,7 @@ where context, ) } + +#[derive(Debug, Deref, Display, RefCast)] +#[repr(transparent)] +pub struct Raw(T); diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 682fc57be..83051d095 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -1,10 +1,10 @@ -use std::{char, marker::PhantomData, rc::Rc, thread::JoinHandle, convert::Infallible}; +use std::{char, convert::Infallible, marker::PhantomData, rc::Rc, thread::JoinHandle}; use derive_more::with_trait::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; use crate::{ - GraphQLScalar, + GraphQLScalar, Raw, ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, graphql_scalar, @@ -16,7 +16,7 @@ use crate::{ base::{GraphQLType, GraphQLValue}, subscriptions::GraphQLSubscriptionValue, }, - value::{ParseScalarResult, ScalarValue, Value}, + value::{ParseScalarResult, ScalarValue, Value, WrongInputScalarTypeError}, }; /// An ID as defined by the GraphQL specification @@ -26,26 +26,32 @@ use crate::{ Clone, Debug, Deref, Deserialize, Display, Eq, From, GraphQLScalar, Into, PartialEq, Serialize, )] #[deref(forward)] +#[from(Box, String)] +#[into(Box, String)] #[graphql(parse_token(String, i32))] -pub struct ID(String); +pub struct ID(Box); impl ID { fn to_output(&self) -> Value { - Value::scalar(self.0.clone()) + Value::scalar(self.0.clone().into_string()) } - fn from_input(s: &impl ScalarValue) -> Result { - s.as_string() - .or_else(|| s.as_int().as_ref().map(ToString::to_string)) - .map(Self) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) + fn from_input(v: &Raw) -> Result> { + v.as_string() + .or_else(|| v.as_int().as_ref().map(ToString::to_string)) + .map(|s| Self(s.into())) + .ok_or_else(|| WrongInputScalarTypeError { + type_name: arcstr::literal!("String` or `Int"), + input: &**v, + }) } } impl ID { - /// Construct a new ID from anything implementing `Into` + /// Construct a new [`ID`] from anything implementing [`Into`]`<`[`String`]`>`. + #[must_use] pub fn new>(value: S) -> Self { - ID(value.into()) + ID(value.into().into()) } } @@ -175,8 +181,8 @@ type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { use std::convert::Infallible; - use crate::{IntoValue as _, ScalarValue, Value}; use super::ArcStr; + use crate::{IntoValue as _, ScalarValue, Value}; pub(super) fn to_output(v: &ArcStr) -> Value { v.into_value() @@ -194,8 +200,8 @@ type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { use std::convert::Infallible; - use crate::{IntoValue as _, ScalarValue, Value}; use super::CompactString; + use crate::{IntoValue as _, ScalarValue, Value}; pub(super) fn to_output(v: &CompactString) -> Value { v.into_value() diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 385a0254e..f5651521a 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -6,14 +6,17 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use arcstr::ArcStr; use compact_str::CompactString; +pub use self::{ + object::Object, + scalar::{ + AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue, + ScalarValueFmt, TryScalarValueTo, WrongInputScalarTypeError, + }, +}; use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, }; -pub use self::{ - object::Object, - scalar::{AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue, TryScalarValueTo, ScalarValueFmt, WrongInputScalarTypeError}, -}; /// Serializable value returned from query and field execution. /// @@ -193,9 +196,7 @@ impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Null => write!(f, "null"), - Self::Scalar(s) => { - fmt::Display::fmt(&ScalarValueFmt(s), f) - } + Self::Scalar(s) => fmt::Display::fmt(&ScalarValueFmt(s), f), Self::List(list) => { write!(f, "[")?; for (idx, item) in list.iter().enumerate() { diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index e81e52aa5..9bb8b7b7f 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -1,17 +1,21 @@ use std::convert::Infallible; +use arcstr::ArcStr; +use derive_more::with_trait::{Display, Error, From}; +use ref_cast::RefCast as _; +use serde::{Serialize, de::DeserializeOwned}; use std::{ any::{Any, TypeId}, borrow::Cow, fmt, ptr, }; -use arcstr::ArcStr; -use derive_more::with_trait::{Display, Error, From}; -use serde::{Serialize, de::DeserializeOwned}; -use crate::{FieldError, parser::{ParseError, ScalarToken}, IntoFieldError}; +use crate::{ + FieldError, IntoFieldError, Raw, + parser::{ParseError, ScalarToken}, +}; #[cfg(doc)] -use crate::{Value, GraphQLValue}; +use crate::{GraphQLValue, Value}; pub use juniper_codegen::ScalarValue; @@ -294,9 +298,17 @@ impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me S> for S { } } +impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me Raw> for S { + type Error = Infallible; + + fn try_scalar_value_to(&'me self) -> Result<&'me Raw, Self::Error> { + Ok(Raw::ref_cast(self)) + } +} + /// Error of a [`ScalarValue`] not matching the expected type. #[derive(Clone, Debug, Display, Error)] -#[display("Expected `{type_name}` scalar, found: {}", ScalarValueFmt(*input))] +#[display("Expected `{type_name}`, found: {}", ScalarValueFmt(*input))] pub struct WrongInputScalarTypeError<'a, S: ScalarValue> { /// Type name of the expected GraphQL scalar. pub type_name: ArcStr, diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index 3331496d4..b543e0d48 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -265,13 +265,11 @@ impl Definition { generics.params.push(parse_quote! { #ref_lt }); let (lt_impl_gens, _, _) = generics.split_for_impl(); - let methods = [ - ( - Method::IntoString, - quote! { fn into_string(self) -> ::core::option::Option<::std::string::String> }, - quote! { ::std::string::String::from(v) }, - ), - ]; + let methods = [( + Method::IntoString, + quote! { fn into_string(self) -> ::core::option::Option<::std::string::String> }, + quote! { ::std::string::String::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(); @@ -328,13 +326,13 @@ impl Definition { } else { default_expr.clone() }; - quote! { - #arm_pattern => ::core::result::Result::Ok(#call), + quote! { + #arm_pattern => ::core::result::Result::Ok(#call), } }); quote! { #[automatically_derived] - impl #lt_impl_gens ::juniper::TryScalarValueTo<#ref_lt, #as_ty> + impl #lt_impl_gens ::juniper::TryScalarValueTo<#ref_lt, #as_ty> for #ty_ident #ty_gens #where_clause { type Error = ::juniper::WrongInputScalarTypeError<#ref_lt, #ty_ident #ty_gens>; @@ -370,7 +368,7 @@ impl Definition { #( #methods )* #from_displayable } - + #( #impls )* } } From 9af448fcd96ad8ff0472da6103fec28834bb2bd7 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Jun 2025 18:20:39 +0200 Subject: [PATCH 05/22] Apply `ScalarValue` input polymorphism, vol.3 [skip ci] --- juniper/src/ast.rs | 33 ---- juniper/src/executor/look_ahead.rs | 4 +- juniper/src/executor/mod.rs | 4 +- juniper/src/executor_tests/variables.rs | 2 +- juniper/src/integrations/bigdecimal.rs | 6 +- juniper/src/integrations/rust_decimal.rs | 6 +- .../src/schema/translate/graphql_parser.rs | 8 +- juniper/src/types/scalars.rs | 4 +- juniper/src/validation/input_value.rs | 4 +- juniper/src/value/mod.rs | 27 +-- juniper/src/value/scalar.rs | 155 +++++++++++++----- juniper_codegen/src/graphql_enum/mod.rs | 2 +- juniper_codegen/src/lib.rs | 20 +-- tests/integration/Cargo.toml | 1 + .../tests/codegen_scalar_attr_derive_input.rs | 98 ++++------- .../tests/codegen_scalar_attr_type_alias.rs | 105 ++++-------- .../tests/codegen_scalar_derive.rs | 100 ++++------- .../tests/codegen_scalar_value_derive.rs | 11 +- tests/integration/tests/common/mod.rs | 12 +- 19 files changed, 271 insertions(+), 331 deletions(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 6e8991295..500ef072d 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -359,30 +359,6 @@ impl InputValue { } } - /// View the underlying int value, if present. - pub fn as_int_value(&self) -> Option - where - S: ScalarValue, - { - self.as_scalar_value().and_then(|s| s.as_int()) - } - - /// View the underlying float value, if present. - pub fn as_float_value(&self) -> Option - where - S: ScalarValue, - { - self.as_scalar_value().and_then(|s| s.as_float()) - } - - /// View the underlying string value, if present. - pub fn as_string_value(&self) -> Option<&str> - where - S: ScalarValue, - { - self.as_scalar_value().and_then(|s| s.as_str()) - } - /// View the underlying scalar value, if present. pub fn as_scalar(&self) -> Option<&S> { match self { @@ -391,15 +367,6 @@ impl InputValue { } } - /// View the underlying scalar value, if present. - pub fn as_scalar_value<'a, T>(&'a self) -> Option<&'a T> - where - T: 'a, - Option<&'a T>: From<&'a S>, - { - self.as_scalar().and_then(Into::into) - } - /// Converts this [`InputValue`] to a [`Spanning::unlocated`] object value. /// /// This constructs a new [`IndexMap`] containing references to the keys diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 8c815a5c9..156a63789 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -772,7 +772,7 @@ impl<'a, S: ScalarValue> ChildrenBuilder<'a, '_, S> { LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) .item { - s.as_bool().unwrap_or(false) + s.try_to_bool().unwrap_or(false) } else { false } @@ -788,7 +788,7 @@ impl<'a, S: ScalarValue> ChildrenBuilder<'a, '_, S> { LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) .item { - b.as_bool().map(Not::not).unwrap_or(false) + b.try_to_bool().map(Not::not).unwrap_or(false) } else { false } diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index c14a0db86..ca00741eb 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -207,10 +207,10 @@ impl FieldError { /// Maps the [`ScalarValue`] type of this [`FieldError`] into the specified /// one. #[must_use] - pub fn map_scalar_value(self) -> FieldError + pub fn map_scalar_value(self) -> FieldError where S: ScalarValue, - Into: ScalarValue, + T: ScalarValue, { FieldError { message: self.message, diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index cda442978..4775db290 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -19,7 +19,7 @@ impl TestComplexScalar { } fn from_input(s: &impl ScalarValue) -> Result { - s.as_str() + s.try_as_str() .filter(|s| *s == "SerializedValue") .map(|_| Self) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {s}"#)) diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index cffff7945..0e32fd3d0 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -44,16 +44,16 @@ mod bigdecimal_scalar { } pub(super) fn from_input(v: &Raw) -> Result> { - if let Some(i) = v.as_int() { + if let Some(i) = v.try_to_int() { Ok(BigDecimal::from(i)) - } else if let Some(f) = v.as_float() { + } else if let Some(f) = v.try_to_float() { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); BigDecimal::from_str(buf.format(f)) .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into()) } else { - v.as_str() + v.try_as_str() .ok_or_else(|| { WrongInputScalarTypeError { type_name: arcstr::literal!("String"), diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index beafcb7b5..e23e2d1bd 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -42,13 +42,13 @@ mod rust_decimal_scalar { } pub(super) fn from_input(v: &Raw) -> Result> { - if let Some(i) = v.as_int() { + if let Some(i) = v.try_to_int() { Ok(Decimal::from(i)) - } else if let Some(f) = v.as_float() { + } else if let Some(f) = v.try_to_float() { Decimal::try_from(f) .map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}").into()) } else { - v.as_str() + v.try_as_str() .ok_or_else(|| { WrongInputScalarTypeError { type_name: arcstr::literal!("String"), diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index 9ad12d553..d62c263d5 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -107,13 +107,13 @@ impl GraphQLParserTranslator { match input { InputValue::Null => ExternalValue::Null, InputValue::Scalar(x) => { - if let Some(v) = x.as_string() { + if let Some(v) = x.try_to_string() { ExternalValue::String(v) - } else if let Some(v) = x.as_int() { + } else if let Some(v) = x.try_to_int() { ExternalValue::Int(ExternalNumber::from(v)) - } else if let Some(v) = x.as_float() { + } else if let Some(v) = x.try_to_float() { ExternalValue::Float(v) - } else if let Some(v) = x.as_bool() { + } else if let Some(v) = x.try_to_bool() { ExternalValue::Boolean(v) } else { panic!("unknown argument type") diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 83051d095..b2f9a46df 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -37,8 +37,8 @@ impl ID { } fn from_input(v: &Raw) -> Result> { - v.as_string() - .or_else(|| v.as_int().as_ref().map(ToString::to_string)) + v.try_to_string() + .or_else(|| v.try_to_int().as_ref().map(ToString::to_string)) .map(|s| Self(s.into())) .ok_or_else(|| WrongInputScalarTypeError { type_name: arcstr::literal!("String` or `Int"), diff --git a/juniper/src/validation/input_value.rs b/juniper/src/validation/input_value.rs index b0a5c2d64..30fec7ecb 100644 --- a/juniper/src/validation/input_value.rs +++ b/juniper/src/validation/input_value.rs @@ -250,8 +250,8 @@ where match value { // TODO: avoid this bad duplicate as_str() call. (value system refactor) - InputValue::Scalar(scalar) if scalar.as_str().is_some() => { - if let Some(name) = scalar.as_str() { + InputValue::Scalar(scalar) if scalar.try_as_str().is_some() => { + if let Some(name) = scalar.try_as_str() { if !meta.values.iter().any(|ev| ev.name == *name) { errors.push(unification_error( var_name, diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index f5651521a..a2015f4fd 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -77,17 +77,6 @@ impl Value { } } - /// View the underlying float value, if present. - pub fn as_float_value(&self) -> Option - where - S: ScalarValue, - { - match self { - Self::Scalar(s) => s.as_float(), - _ => None, - } - } - /// View the underlying object value, if present. pub fn as_object_value(&self) -> Option<&Object> { match self { @@ -139,18 +128,16 @@ impl Value { } /// Maps the [`ScalarValue`] type of this [`Value`] into the specified one. - pub fn map_scalar_value(self) -> Value + pub fn map_scalar_value(self) -> Value where S: ScalarValue, - Into: ScalarValue, + T: ScalarValue, { - if TypeId::of::() == TypeId::of::() { - // SAFETY: This is safe, because we're transmuting the value into - // itself, so no invariants may change and we're just - // satisfying the type checker. - // As `mem::transmute_copy` creates a copy of data, we need - // `mem::ManuallyDrop` here to omit double-free when - // `S: Drop`. + if TypeId::of::() == TypeId::of::() { + // SAFETY: This is safe, because we're transmuting the value into itself, so no + // invariants may change, and we're just satisfying the type checker. + // As `mem::transmute_copy` creates a copy of the data, we need the + // `mem::ManuallyDrop` here to omit double-free when `S: Drop`. let val = mem::ManuallyDrop::new(self); unsafe { mem::transmute_copy(&*val) } } else { diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 9bb8b7b7f..5a8eeeb4a 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -10,10 +10,7 @@ use std::{ fmt, ptr, }; -use crate::{ - FieldError, IntoFieldError, Raw, - parser::{ParseError, ScalarToken}, -}; +use crate::{FieldError, IntoFieldError, Raw, parser::{ParseError, ScalarToken}}; #[cfg(doc)] use crate::{GraphQLValue, Value}; @@ -193,73 +190,141 @@ pub trait ScalarValue: fn is_type<'a, T>(&'a self) -> bool where T: 'a, - Option<&'a T>: From<&'a Self>, + Self: TryScalarValueTo<'a, &'a T, Error: IntoFieldError>, { - >::from(self).is_some() + self.try_scalar_value_to().is_ok() } - /// Represents this [`ScalarValue`] as an integer value. + /// Tries to represent this [`ScalarValue`] as the specified type `T`. + /// + /// This method could be used instead of other helpers in case the [`TryScalarValueTo::Error`] + /// is needed. + /// + /// # Implementation /// - /// This function is used for implementing [`GraphQLValue`] for [`i32`] for - /// all possible [`ScalarValue`]s. Implementations should convert all the - /// supported integer types with 32 bit or less to an integer, if requested. + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`` conversion. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`` conversion directly. + fn try_to<'a, T>(&'a self) -> Result>::Error> + where + T: 'a, + Self: TryScalarValueTo<'a, T, Error: IntoFieldError>, + { + self.try_scalar_value_to() + } + + /// Tries to represent this [`ScalarValue`] as a [`bool`] value. + /// + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// needed. + /// + /// # Implementation + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`bool`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`bool`] for all possible + /// [`ScalarValue`]s. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<`[`bool`]`>` conversions for all the supported boolean types. #[must_use] - fn as_int(&self) -> Option { - self.try_scalar_value_to().ok() + fn try_to_bool(&self) -> Option { + self.try_to().ok() } - /// Represents this [`ScalarValue`] as a [`String`] value. + /// Tries to represent this [`ScalarValue`] as an [`i32`] value. + /// + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// needed. /// - /// This function is used for implementing [`GraphQLValue`] for [`String`] - /// for all possible [`ScalarValue`]s. + /// # Implementation + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`i32`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`i32`] for all possible + /// [`ScalarValue`]s. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<`[`i32`]`>` conversions for all the supported integer types with + /// 32 bit or less to an integer, if requested. #[must_use] - fn as_string(&self) -> Option { - self.try_scalar_value_to().ok() + fn try_to_int(&self) -> Option { + self.try_to().ok() } - /// Converts this [`ScalarValue`] into a [`String`] value. + /// Tries to represent this [`ScalarValue`] as a [`f64`] value. /// - /// Same as [`ScalarValue::as_string()`], but takes ownership, so allows to - /// omit redundant cloning. + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// needed. + /// + /// # Implementation + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`f64`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`f64`] for all possible + /// [`ScalarValue`]s. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<`[`f64`]`>` conversions for all the supported integer types with + /// 64 bit and all floating point values with 64 bit or less to a float, if requested. #[must_use] - fn into_string(self) -> Option; + fn try_to_float(&self) -> Option { + self.try_to().ok() + } - /// Represents this [`ScalarValue`] as a [`str`] value. + /// Tries to represent this [`ScalarValue`] as a [`String`] value. + /// + /// Allocates every time is called. For read-only and non-owning use of the underlying + /// [`String`] value, consider using the [`ScalarValue::try_as_str()`] method. + /// + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] + /// is needed. /// - /// This function is used for implementing [`GraphQLValue`] for [`str`] for - /// all possible [`ScalarValue`]s. + /// # Implementation + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`String`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`String`] for all possible + /// [`ScalarValue`]s. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<`[`String`]`>` conversions for all the supported string types, if + /// requested. #[must_use] - fn as_str(&self) -> Option<&str> { - self.try_scalar_value_to().ok() + fn try_to_string(&self) -> Option { + self.try_to().ok() } - /// Represents this [`ScalarValue`] as a float value. + /// Converts this [`ScalarValue`] into a [`String`] value. /// - /// This function is used for implementing [`GraphQLValue`] for [`f64`] for - /// all possible [`ScalarValue`]s. Implementations should convert all - /// supported integer types with 64 bit or less and all floating point - /// values with 64 bit or less to a float, if requested. + /// Same as [`ScalarValue::try_to_string()`], but takes ownership, so allows to + /// omit redundant cloning. #[must_use] - fn as_float(&self) -> Option { - self.try_scalar_value_to().ok() - } + fn into_string(self) -> Option; - /// Represents this [`ScalarValue`] as a boolean value + /// Tries to represent this [`ScalarValue`] as a [`str`] value. + /// + /// Use the [`ScalarValue::try_to::<&str>()`] method in case the [`TryScalarValueTo::Error`] + /// is needed. + /// + /// # Implementation + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<&`[`str`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`String`] for all possible + /// [`ScalarValue`]s. /// - /// This function is used for implementing [`GraphQLValue`] for [`bool`] for - /// all possible [`ScalarValue`]s. + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<&`[`str`]`>` conversions for all the supported string types, if + /// requested. #[must_use] - fn as_bool(&self) -> Option { - self.try_scalar_value_to().ok() + fn try_as_str(&self) -> Option<&str> { + self.try_to().ok() } /// Converts this [`ScalarValue`] into another one. fn into_another(self) -> S { - if let Some(i) = self.as_int() { + if let Some(i) = self.try_to_int() { S::from(i) - } else if let Some(f) = self.as_float() { + } else if let Some(f) = self.try_to_float() { S::from(f) - } else if let Some(b) = self.as_bool() { + } else if let Some(b) = self.try_to_bool() { S::from(b) } else if let Some(s) = self.into_string() { S::from(s) @@ -327,7 +392,7 @@ pub struct ScalarValueFmt<'a, S: ScalarValue>(pub &'a S); impl<'a, S: ScalarValue> Display for ScalarValueFmt<'a, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(s) = self.0.as_str() { + if let Some(s) = self.0.try_as_str() { write!(f, "\"{s}\"") } else { Display::fmt(&self.0, f) @@ -372,7 +437,7 @@ pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value. /// /// [0]: https://spec.graphql.org/October2021#sec-Int - #[from(i32)] + #[from] #[value(as_float, as_int)] Int(i32), @@ -381,7 +446,7 @@ pub enum DefaultScalarValue { /// /// [0]: https://spec.graphql.org/October2021#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point - #[from(f64)] + #[from] #[value(as_float)] Float(f64), diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs index 2474f3a95..12742c498 100644 --- a/juniper_codegen/src/graphql_enum/mod.rs +++ b/juniper_codegen/src/graphql_enum/mod.rs @@ -594,7 +594,7 @@ impl Definition { fn from_input_value( v: &::juniper::InputValue<#scalar>, ) -> ::core::result::Result { - match v.as_enum_value().or_else(|| v.as_string_value()) { + match v.as_enum_value().or_else(|| v.as_scalar()?.try_as_str()) { #( #variants )* _ => ::core::result::Result::Err( ::std::format!("Unknown enum value: {}", v), diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index cc9d58736..c4ca04b10 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -453,7 +453,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// input: &impl ScalarValue, /// ) -> Result { /// // ^^^^^^ must implement `IntoFieldError` -/// input.as_str() +/// input.try_as_str() /// .ok_or_else(|| format!("Expected `String`, found: {input}")) /// .and_then(|str| { /// str.strip_prefix("id: ") @@ -502,9 +502,9 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// /// fn from_input(v: &impl ScalarValue) -> Result { -/// v.as_string() +/// v.try_to_string() /// .map(StringOrInt::String) -/// .or_else(|| v.as_int().map(StringOrInt::Int)) +/// .or_else(|| v.try_to_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -545,9 +545,9 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// /// pub(super) fn from_input(v: &impl ScalarValue) -> Result { -/// v.as_string() +/// v.try_to_string() /// .map(StringOrInt::String) -/// .or_else(|| v.as_int().map(StringOrInt::Int)) +/// .or_else(|| v.try_to_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -583,9 +583,9 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// /// fn from_input(v: &impl ScalarValue) -> Result { -/// v.as_string() +/// v.try_to_string() /// .map(Self::String) -/// .or_else(|| v.as_int().map(Self::Int)) +/// .or_else(|| v.try_to_int().map(Self::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -631,9 +631,9 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// /// pub(super) fn from_input(v: &impl ScalarValue) -> Result { -/// v.as_string() +/// v.try_to_string() /// .map(StringOrInt::String) -/// .or_else(|| v.as_int().map(StringOrInt::Int)) +/// .or_else(|| v.try_to_int().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) /// } /// @@ -741,7 +741,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// } /// /// pub(super) fn from_input(v: &CustomScalarValue) -> Result { -/// v.as_str() +/// v.try_as_str() /// .ok_or_else(|| format!("Expected `String`, found: {v}")) /// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) /// } diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index c977e0025..bd30a38c7 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -6,6 +6,7 @@ publish = false [dev-dependencies] chrono = { version = "0.4", default-features = false } +derive_more = { version = "2.0", features = ["display", "from"] } futures = "0.3" itertools = "0.14" juniper = { path = "../../juniper", features = ["chrono"] } diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index 4a2116c4e..773f281bc 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -4,12 +4,12 @@ pub mod common; -use std::fmt; +use std::{fmt, convert::Infallible}; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, graphql_object, - graphql_scalar, graphql_value, graphql_vars, + graphql_scalar, graphql_value, graphql_vars, Raw, }; use self::common::{ @@ -31,10 +31,8 @@ mod trivial { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -239,10 +237,8 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Counter(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -314,10 +310,8 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,10 +384,8 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(v: &impl ScalarValue) -> prelude::Result { - v.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {v}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -468,11 +460,11 @@ mod multiple_delegated_parse_token { } } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_string() + fn from_input(v: &Raw) -> prelude::Result> { + v.try_to_string() .map(Self::String) - .or_else(|| s.as_int().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) + .or_else(|| v.try_to_int().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } } @@ -531,18 +523,14 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(s: &impl ScalarValue) -> prelude::Result, prelude::String> + fn from_input(s: &str) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } struct QueryRoot; @@ -600,10 +588,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -689,19 +675,15 @@ mod with_module { } pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result, prelude::String> + s: &str, + ) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } } @@ -761,10 +743,8 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -837,10 +817,8 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -913,10 +891,8 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -989,10 +965,8 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -1064,10 +1038,8 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 09411d702..5fcecf3f9 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -2,12 +2,12 @@ pub mod common; -use std::fmt; +use std::{convert::Infallible, fmt}; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, graphql_object, - graphql_scalar, graphql_value, graphql_vars, + ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, execute, + graphql_object, graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ @@ -20,6 +20,7 @@ use self::common::hygiene::*; mod all_custom_resolvers { use super::*; + use std::convert::Infallible; struct CustomCounter(i32); @@ -36,10 +37,8 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -117,10 +116,8 @@ mod explicit_name { Value::scalar(v.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -217,10 +214,8 @@ mod delegated_parse_token { Value::scalar(v.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } struct QueryRoot; @@ -299,11 +294,11 @@ mod multiple_delegated_parse_token { } } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_string() + fn from_input(v: &Raw) -> prelude::Result> { + v.try_to_string() .map(StringOrInt::String) - .or_else(|| s.as_int().map(StringOrInt::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) + .or_else(|| v.try_to_int().map(StringOrInt::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } struct QueryRoot; @@ -363,18 +358,14 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(s: &impl ScalarValue) -> prelude::Result, prelude::String> + fn from_input(s: &str) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } struct QueryRoot; @@ -434,10 +425,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -525,19 +514,15 @@ mod with_module { } pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result, prelude::String> + s: &str, + ) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - s.as_str() - .ok_or_else(|| format!("Expected `String`, found: {s}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } } @@ -601,12 +586,8 @@ mod description_from_doc_comment { Value::scalar(v.0) } - pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + pub(super) fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } } @@ -687,12 +668,8 @@ mod description_from_attribute { Value::scalar(v.0) } - pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + pub(super) fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } } @@ -773,12 +750,8 @@ mod custom_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + pub(super) fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } } @@ -859,12 +832,8 @@ mod generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + pub(super) fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } } @@ -945,12 +914,8 @@ mod bounded_generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input( - s: &impl ScalarValue, - ) -> prelude::Result { - s.as_int() - .map(CustomCounter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + pub(super) fn from_input(i: i32) -> prelude::Result { + Ok(CustomCounter(i)) } } diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index 6a18ab1f0..caf34e979 100644 --- a/tests/integration/tests/codegen_scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -2,12 +2,12 @@ pub mod common; -use std::fmt; +use std::{convert::Infallible, fmt}; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, - graphql_object, graphql_value, graphql_vars, + GraphQLScalar, ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, + execute, graphql_object, graphql_value, graphql_vars, }; use self::common::{ @@ -29,10 +29,8 @@ mod trivial { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -237,10 +235,8 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Counter(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -313,10 +309,8 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,10 +384,8 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -469,11 +461,11 @@ mod multiple_delegated_parse_token { } } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_string() + fn from_input(v: &Raw) -> prelude::Result> { + v.try_to_string() .map(Self::String) - .or_else(|| s.as_int().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {s}")) + .or_else(|| v.try_to_int().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } } @@ -533,18 +525,14 @@ mod where_attribute { Value::scalar(v.0.to_rfc3339()) } - fn from_input(v: &impl ScalarValue) -> prelude::Result, prelude::String> + fn from_input(s: &str) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_str() - .ok_or_else(|| format!("Expected `String`, found: {v}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } struct QueryRoot; @@ -603,10 +591,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -693,19 +679,15 @@ mod with_module { } pub(super) fn from_input( - v: &impl ScalarValue, - ) -> prelude::Result, prelude::String> + s: &str, + ) -> prelude::Result, prelude::Box> where Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_str() - .ok_or_else(|| format!("Expected `String`, found: {v}")) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}")) - }) + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {e}").into()) } } @@ -766,10 +748,8 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -843,10 +823,8 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -920,10 +898,8 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -997,10 +973,8 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } @@ -1073,10 +1047,8 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(s: &impl ScalarValue) -> prelude::Result { - s.as_int() - .map(Self) - .ok_or_else(|| format!("Expected `String`, found: {s}")) + fn from_input(i: i32) -> prelude::Result { + Ok(Self(i)) } } diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index 936318cbe..6dbf35247 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -2,6 +2,7 @@ pub mod common; +use derive_more::with_trait::{Display, From} use juniper::{DefaultScalarValue, ScalarValue}; use serde::{Deserialize, Serialize}; @@ -11,7 +12,7 @@ use self::common::hygiene::*; mod trivial { use super::*; - #[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -52,7 +53,7 @@ mod trivial { mod named_fields { use super::*; - #[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -93,7 +94,7 @@ mod named_fields { mod custom_fn { use super::*; - #[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -138,7 +139,7 @@ mod custom_fn { mod allow_missing_attributes { use super::*; - #[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] #[value(allow_missing_attributes)] pub enum CustomScalarValue { @@ -153,7 +154,7 @@ mod allow_missing_attributes { #[test] fn into_another() { - assert!(CustomScalarValue::Int(5).as_int().is_none()); + assert!(CustomScalarValue::Int(5).to_int().is_none()); assert!( CustomScalarValue::from(0.5_f64) .into_another::() diff --git a/tests/integration/tests/common/mod.rs b/tests/integration/tests/common/mod.rs index 3fdabb8d7..d9f995e85 100644 --- a/tests/integration/tests/common/mod.rs +++ b/tests/integration/tests/common/mod.rs @@ -1,5 +1,6 @@ use std::fmt; +use derive_more::with_trait::{Display, From}; use juniper::{InputValue, IntoInputValue, IntoValue, ScalarValue, Value}; use serde::{Deserialize, Deserializer, Serialize, de}; use smartstring::alias::CompactString; @@ -75,16 +76,25 @@ pub mod util { } } -#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] +#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize)] #[serde(untagged)] pub enum MyScalarValue { + #[from] #[value(as_float, as_int)] Int(i32), + + #[from] Long(i64), + + #[from] #[value(as_float)] Float(f64), + + #[from(&str, String)] #[value(as_str, as_string, into_string)] String(String), + + #[from] #[value(as_bool)] Boolean(bool), } From 65ea0a0fe6f180e99072df615723e8612da7d7dc Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Jun 2025 19:04:28 +0200 Subject: [PATCH 06/22] Remake `ScalarValue::is_type()` --- juniper/src/value/scalar.rs | 8 +- juniper_codegen/src/scalar_value/mod.rs | 208 +++--------------- .../tests/codegen_scalar_value_derive.rs | 4 +- 3 files changed, 28 insertions(+), 192 deletions(-) diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 5a8eeeb4a..672890a15 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -187,13 +187,7 @@ pub trait ScalarValue: /// assert_eq!(value.is_type::(), false); /// ``` #[must_use] - fn is_type<'a, T>(&'a self) -> bool - where - T: 'a, - Self: TryScalarValueTo<'a, &'a T, Error: IntoFieldError>, - { - self.try_scalar_value_to().is_ok() - } + fn is_type(&self) -> bool; /// Tries to represent this [`ScalarValue`] as the specified type `T`. /// diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index b543e0d48..911dd7cc3 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -9,7 +9,6 @@ use syn::{ parse_quote, spanned::Spanned as _, token, - visit::Visit, }; use crate::common::{ @@ -247,8 +246,6 @@ struct Definition { 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); } } @@ -354,17 +351,38 @@ impl Definition { let from_displayable = self.from_displayable.as_ref().map(|expr| { quote! { - fn from_displayable( - __s: &Str, - ) -> Self { - #expr(__s) + fn from_displayable< + __T: ::core::fmt::Display + ::core::any::Any + ?::core::marker::Sized, + >(__v: &__T) -> Self { + #expr(__v) } } }); + let is_type = { + let arms = self.variants.iter().map(|var| { + let var_ident = &var.ident; + let field = Field::try_from(var.fields.clone()).unwrap(); + let var_pattern = field.match_arg(); + + quote! { + Self::#var_ident #var_pattern => ::juniper::AnyExt::is::<__T>(v), + } + }); + + quote! { + fn is_type<__T: ::core::any::Any + ?::core::marker::Sized>(&self) -> bool { + match self { + #( #arms )* + } + } + } + }; + quote! { #[automatically_derived] impl #impl_gens ::juniper::ScalarValue for #ty_ident #ty_gens #where_clause { + #is_type #( #methods )* #from_displayable } @@ -372,142 +390,6 @@ impl Definition { #( #impls )* } } - - /// 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 var_name = var_ident.to_string(); - 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 - } - } - } -/* - #[automatically_derived] - impl #lf_impl_gen ::juniper::TryFromScalarValue<&'___a #ty_ident #ty_gen> - for &'___a #var_ty #where_clause - { - type Error = ::juniper::WrongInputScalarTypeError<'___a, #ty_ident #ty_gen>; - - fn try_from_scalar_value( - __value: &'___a #ty_ident #ty_gen - ) -> ::core::result::Result { - if let #ty_ident::#var_ident #var_field = __value { - ::core::result::Result::Ok(v) - } else { - ::core::result::Result::Err(::juniper::WrongInputScalarTypeError { - type_name: ::juniper::arcstr::literal!(#var_name), - input: __value, - }) - } - } - }*/ - } - }) - .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. @@ -575,43 +457,3 @@ impl Field { } } } - -/// [`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> Visit<'ast> for IsVariantGeneric<'_> { - 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); - } - } - } -} diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index 6dbf35247..5eb97fb39 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -2,7 +2,7 @@ pub mod common; -use derive_more::with_trait::{Display, From} +use derive_more::with_trait::{Display, From}; use juniper::{DefaultScalarValue, ScalarValue}; use serde::{Deserialize, Serialize}; @@ -154,7 +154,7 @@ mod allow_missing_attributes { #[test] fn into_another() { - assert!(CustomScalarValue::Int(5).to_int().is_none()); + assert!(CustomScalarValue::Int(5).try_to_int().is_none()); assert!( CustomScalarValue::from(0.5_f64) .into_another::() From e7f7388e1972bf115f04dfeb506d20b6b1066827 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Jun 2025 19:59:16 +0200 Subject: [PATCH 07/22] Rework `ScalarValue::into_string()` too [skip ci] --- juniper/Cargo.toml | 2 +- juniper/src/value/scalar.rs | 96 ++++++++++++------- juniper_codegen/src/scalar_value/mod.rs | 22 ----- tests/integration/Cargo.toml | 2 +- .../tests/codegen_scalar_value_derive.rs | 10 +- tests/integration/tests/common/mod.rs | 13 +-- 6 files changed, 68 insertions(+), 77 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index cf2dd364e..ae5b131e2 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -51,7 +51,7 @@ bson = { version = "2.4", optional = true } chrono = { version = "0.4.30", features = ["alloc"], default-features = false, optional = true } chrono-tz = { version = "0.10", default-features = false, optional = true } compact_str = "0.9" -derive_more = { version = "2.0", features = ["debug", "deref", "display", "error", "from", "into", "into_iterator"] } +derive_more = { version = "2.0", features = ["debug", "deref", "display", "error", "from", "into", "into_iterator", "try_into"] } fnv = "1.0.5" futures = { version = "0.3.22", features = ["alloc"], default-features = false } graphql-parser = { version = "0.4", optional = true } diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 672890a15..b0e71ff32 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use arcstr::ArcStr; -use derive_more::with_trait::{Display, Error, From}; +use derive_more::with_trait::{Display, Error, From, TryInto}; use ref_cast::RefCast as _; use serde::{Serialize, de::DeserializeOwned}; use std::{ @@ -50,10 +50,11 @@ pub trait ParseScalarValue { /// # use std::{any::Any, fmt}; /// # /// # use compact_str::CompactString; -/// # use juniper::ScalarValue; -/// # use serde::{de, Deserialize, Deserializer, Serialize}; -/// # -/// #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] +/// use derive_more::{Display, From, TryInto}; +/// use juniper::ScalarValue; +/// use serde::{de, Deserialize, Deserializer, Serialize}; +/// +/// #[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] /// #[serde(untagged)] /// #[value(from_displayable_with = from_compact_str)] /// enum MyScalarValue { @@ -173,12 +174,20 @@ pub trait ScalarValue: + for<'a> TryScalarValueTo<'a, String, Error: IntoFieldError> + for<'a> TryScalarValueTo<'a, &'a str, Error: IntoFieldError> + for<'a> TryScalarValueTo<'a, &'a Self, Error: IntoFieldError> + + TryInto + 'static { - /// Checks whether this [`ScalarValue`] contains the value of the given - /// type. + /// Checks whether this [`ScalarValue`] contains the value of the provided type `T`. /// - /// ``` + /// # Implementation + /// + /// Implementations should implement this method. + /// + /// This is usually an enum dispatch with calling [`AnyExt::is::()`] method on each variant. + /// + /// # Example + /// + /// ```rust /// # use juniper::{ScalarValue, DefaultScalarValue}; /// # /// let value = DefaultScalarValue::Int(42); @@ -196,10 +205,10 @@ pub trait ScalarValue: /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`` conversion. + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion. /// /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`` conversion directly. + /// [`TryScalarValueTo`] conversion directly. fn try_to<'a, T>(&'a self) -> Result>::Error> where T: 'a, @@ -215,12 +224,11 @@ pub trait ScalarValue: /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`bool`]`>` conversion, - /// which is used for implementing [`GraphQLValue`] for [`bool`] for all possible - /// [`ScalarValue`]s. + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// used for implementing [`GraphQLValue`] for [`bool`] for all possible [`ScalarValue`]s. /// /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`<`[`bool`]`>` conversions for all the supported boolean types. + /// [`TryScalarValueTo`] conversions for all the supported boolean types. #[must_use] fn try_to_bool(&self) -> Option { self.try_to().ok() @@ -233,13 +241,12 @@ pub trait ScalarValue: /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`i32`]`>` conversion, - /// which is used for implementing [`GraphQLValue`] for [`i32`] for all possible - /// [`ScalarValue`]s. + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// used for implementing [`GraphQLValue`] for [`i32`] for all possible [`ScalarValue`]s. /// /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`<`[`i32`]`>` conversions for all the supported integer types with - /// 32 bit or less to an integer, if requested. + /// [`TryScalarValueTo`] conversions for all the supported integer types with 32 bit or + /// less to an integer, if requested. #[must_use] fn try_to_int(&self) -> Option { self.try_to().ok() @@ -252,13 +259,12 @@ pub trait ScalarValue: /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`f64`]`>` conversion, - /// which is used for implementing [`GraphQLValue`] for [`f64`] for all possible - /// [`ScalarValue`]s. + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// used for implementing [`GraphQLValue`] for [`f64`] for all possible [`ScalarValue`]s. /// /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`<`[`f64`]`>` conversions for all the supported integer types with - /// 64 bit and all floating point values with 64 bit or less to a float, if requested. + /// [`TryScalarValueTo`] conversions for all the supported integer types with 64 bit and + /// all floating point values with 64 bit or less to a float, if requested. #[must_use] fn try_to_float(&self) -> Option { self.try_to().ok() @@ -274,24 +280,33 @@ pub trait ScalarValue: /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<`[`String`]`>` conversion, - /// which is used for implementing [`GraphQLValue`] for [`String`] for all possible - /// [`ScalarValue`]s. + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// used for implementing [`GraphQLValue`] for [`String`] for all possible [`ScalarValue`]s. /// /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`<`[`String`]`>` conversions for all the supported string types, if - /// requested. + /// [`TryScalarValueTo`] conversions for all the supported string types, if requested. #[must_use] fn try_to_string(&self) -> Option { self.try_to().ok() } - /// Converts this [`ScalarValue`] into a [`String`] value. + /// Tries to convert this [`ScalarValue`] into a [`String`] value. + /// + /// Similar to the [`ScalarValue::try_to_string()`] method, but takes ownership, so allows to + /// omit redundant [`Clone`]ing. + /// + /// Use the [`TryInto`] conversion in case the [`TryInto::Error`] is needed. + /// + /// # Implementation /// - /// Same as [`ScalarValue::try_to_string()`], but takes ownership, so allows to - /// omit redundant cloning. + /// This method is an ergonomic alias for the [`TryInto`] conversion. + /// + /// Implementations should not implement this method, but rather implement the + /// [`TryInto`] conversion for all the supported string types, if requested. #[must_use] - fn into_string(self) -> Option; + fn try_into_string(self) -> Option { + self.try_into().ok() + } /// Tries to represent this [`ScalarValue`] as a [`str`] value. /// @@ -312,7 +327,14 @@ pub trait ScalarValue: self.try_to().ok() } - /// Converts this [`ScalarValue`] into another one. + /// Converts this [`ScalarValue`] into another one via [`i32`], [`f64`], [`bool`] or [`String`] + /// conversion. + /// + /// # Panics + /// + /// If this [`ScalarValue`] doesn't represent at least one of [`i32`], [`f64`], [`bool`] or + /// [`String`]. + #[must_use] fn into_another(self) -> S { if let Some(i) = self.try_to_int() { S::from(i) @@ -320,7 +342,7 @@ pub trait ScalarValue: S::from(f) } else if let Some(b) = self.try_to_bool() { S::from(b) - } else if let Some(s) = self.into_string() { + } else if let Some(s) = self.try_into_string() { S::from(s) } else { unreachable!("`ScalarValue` must represent at least one of the GraphQL spec types") @@ -332,7 +354,7 @@ pub trait ScalarValue: /// This method should be implemented if [`ScalarValue`] implementation uses some custom string /// type inside to enable efficient conversion from values of this type. /// - /// Default implementation allocates by converting [`ToString`] and [`From`]`<`[`String`]`>`. + /// Default implementation allocates by converting [`ToString`] and [`From`]. /// /// # Example /// @@ -425,7 +447,7 @@ impl AnyExt for T {} /// These types closely follow the [GraphQL specification][0]. /// /// [0]: https://spec.graphql.org/October2021 -#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize)] +#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value. diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index 911dd7cc3..278087135 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -262,27 +262,6 @@ impl Definition { generics.params.push(parse_quote! { #ref_lt }); let (lt_impl_gens, _, _) = generics.split_for_impl(); - let methods = [( - Method::IntoString, - quote! { fn into_string(self) -> ::core::option::Option<::std::string::String> }, - quote! { ::std::string::String::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, - } - } - } - }); - let methods2 = [ ( Method::AsInt, @@ -383,7 +362,6 @@ impl Definition { #[automatically_derived] impl #impl_gens ::juniper::ScalarValue for #ty_ident #ty_gens #where_clause { #is_type - #( #methods )* #from_displayable } diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index bd30a38c7..0e41e45be 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dev-dependencies] chrono = { version = "0.4", default-features = false } -derive_more = { version = "2.0", features = ["display", "from"] } +derive_more = { version = "2.0", features = ["display", "from", "try_into"] } futures = "0.3" itertools = "0.14" juniper = { path = "../../juniper", features = ["chrono"] } diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index 5eb97fb39..a0397af5b 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -2,7 +2,7 @@ pub mod common; -use derive_more::with_trait::{Display, From}; +use derive_more::with_trait::{Display, From, TryInto}; use juniper::{DefaultScalarValue, ScalarValue}; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ use self::common::hygiene::*; mod trivial { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -53,7 +53,7 @@ mod trivial { mod named_fields { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -94,7 +94,7 @@ mod named_fields { mod custom_fn { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -139,7 +139,7 @@ mod custom_fn { mod allow_missing_attributes { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize)] + #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] #[value(allow_missing_attributes)] pub enum CustomScalarValue { diff --git a/tests/integration/tests/common/mod.rs b/tests/integration/tests/common/mod.rs index d9f995e85..bcec2112b 100644 --- a/tests/integration/tests/common/mod.rs +++ b/tests/integration/tests/common/mod.rs @@ -1,6 +1,6 @@ use std::fmt; -use derive_more::with_trait::{Display, From}; +use derive_more::with_trait::{Display, From, TryInto}; use juniper::{InputValue, IntoInputValue, IntoValue, ScalarValue, Value}; use serde::{Deserialize, Deserializer, Serialize, de}; use smartstring::alias::CompactString; @@ -76,25 +76,16 @@ pub mod util { } } -#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize)] +#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum MyScalarValue { - #[from] #[value(as_float, as_int)] Int(i32), - - #[from] Long(i64), - - #[from] #[value(as_float)] Float(f64), - - #[from(&str, String)] #[value(as_str, as_string, into_string)] String(String), - - #[from] #[value(as_bool)] Boolean(bool), } From 75d33dd2c165c223bee2c1ecae9a6e1c646787dd Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 17:23:04 +0200 Subject: [PATCH 08/22] Bootstrap return-type polymorphism --- juniper/src/macros/helper/mod.rs | 43 +++++++++- juniper/src/types/scalars.rs | 14 +-- juniper/src/value/mod.rs | 2 +- juniper/src/value/scalar.rs | 85 ++++++++++--------- juniper_codegen/src/graphql_scalar/mod.rs | 5 +- juniper_codegen/src/scalar_value/mod.rs | 6 +- .../tests/codegen_scalar_attr_derive_input.rs | 6 +- .../tests/codegen_scalar_value_derive.rs | 16 +++- 8 files changed, 115 insertions(+), 62 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index e3e31926d..b2183d4f4 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,11 +2,14 @@ pub mod subscription; -use std::fmt; +use std::{convert::Infallible, fmt}; use futures::future::{self, BoxFuture}; -use crate::FieldError; +use crate::{ + DefaultScalarValue, FieldError, FieldResult, GraphQLScalar, InputValue, IntoFieldError, + ScalarValue, +}; /// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type /// from a [`Result`]. @@ -53,3 +56,39 @@ where { Box::pin(future::err(err_unnamed_type(name))) } + +/// [Autoref-based specialized][0] coercion into a [`Result`] 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 ToResultCall { + /// Input of this function. + type Input; + /// Output of this function. + type Output; + /// Error of the [`Result`] coercion for this function. + type Error; + + /// Calls this function, coercing its output into a [`Result`]. + fn __to_result_call(&self, input: Self::Input) -> Result; +} + +impl ToResultCall for fn(I) -> O { + type Input = I; + type Output = O; + type Error = Infallible; + + fn __to_result_call(&self, input: Self::Input) -> Result { + Ok(self(input)) + } +} + +impl ToResultCall for &fn(I) -> Result { + type Input = I; + type Output = O; + type Error = E; + + fn __to_result_call(&self, input: Self::Input) -> Result { + self(input) + } +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index b2f9a46df..594a15120 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -480,7 +480,7 @@ impl Default for EmptySubscription { mod tests { use crate::{ parser::ScalarToken, - value::{DefaultScalarValue, ParseScalarValue}, + value::{DefaultScalarValue, ParseScalarValue, ScalarValue as _}, }; use super::{EmptyMutation, EmptySubscription, ID}; @@ -488,20 +488,20 @@ mod tests { #[test] fn test_id_from_string() { let actual = ID::from(String::from("foo")); - let expected = ID(String::from("foo")); + let expected = ID("foo".into()); assert_eq!(actual, expected); } #[test] fn test_id_new() { let actual = ID::new("foo"); - let expected = ID(String::from("foo")); + let expected = ID("foo".into()); assert_eq!(actual, expected); } #[test] fn test_id_deref() { - let id = ID(String::from("foo")); + let id = ID("foo".into()); assert_eq!(id.len(), 3); } @@ -517,7 +517,7 @@ mod tests { let s = >::from_str(ScalarToken::String(s)); assert!(s.is_ok(), "A parsing error occurred: {s:?}"); - let s: Option = s.unwrap().into(); + let s: Option = s.unwrap().try_to().ok(); assert!(s.is_some(), "No string returned"); assert_eq!(s.unwrap(), expected); } @@ -545,7 +545,7 @@ mod tests { let n = >::from_str(ScalarToken::Int(v)); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); - let n: Option = n.unwrap().into(); + let n: Option = n.unwrap().try_to().ok(); assert!(n.is_some(), "No `f64` returned"); assert_eq!(n.unwrap(), f64::from(expected)); } @@ -563,7 +563,7 @@ mod tests { let n = >::from_str(ScalarToken::Float(v)); assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err()); - let n: Option = n.unwrap().into(); + let n: Option = n.unwrap().try_to().ok(); assert!(n.is_some(), "No `f64` returned"); assert_eq!(n.unwrap(), expected); } diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index a2015f4fd..23fb27986 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -134,7 +134,7 @@ impl Value { T: ScalarValue, { if TypeId::of::() == TypeId::of::() { - // SAFETY: This is safe, because we're transmuting the value into itself, so no + // SAFETY: This is safe, because we're transmuting the value into itself, so no // invariants may change, and we're just satisfying the type checker. // As `mem::transmute_copy` creates a copy of the data, we need the // `mem::ManuallyDrop` here to omit double-free when `S: Drop`. diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index b0e71ff32..be516f0e3 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -10,7 +10,10 @@ use std::{ fmt, ptr, }; -use crate::{FieldError, IntoFieldError, Raw, parser::{ParseError, ScalarToken}}; +use crate::{ + FieldError, IntoFieldError, Raw, + parser::{ParseError, ScalarToken}, +}; #[cfg(doc)] use crate::{GraphQLValue, Value}; @@ -180,13 +183,13 @@ pub trait ScalarValue: /// Checks whether this [`ScalarValue`] contains the value of the provided type `T`. /// /// # Implementation - /// + /// /// Implementations should implement this method. - /// + /// /// This is usually an enum dispatch with calling [`AnyExt::is::()`] method on each variant. - /// + /// /// # Example - /// + /// /// ```rust /// # use juniper::{ScalarValue, DefaultScalarValue}; /// # @@ -199,15 +202,15 @@ pub trait ScalarValue: fn is_type(&self) -> bool; /// Tries to represent this [`ScalarValue`] as the specified type `T`. - /// - /// This method could be used instead of other helpers in case the [`TryScalarValueTo::Error`] + /// + /// This method could be used instead of other helpers in case the [`TryScalarValueTo::Error`] /// is needed. /// /// # Implementation /// /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion. /// - /// Implementations should not implement this method, but rather implement the + /// Implementations should not implement this method, but rather implement the /// [`TryScalarValueTo`] conversion directly. fn try_to<'a, T>(&'a self) -> Result>::Error> where @@ -216,18 +219,18 @@ pub trait ScalarValue: { self.try_scalar_value_to() } - + /// Tries to represent this [`ScalarValue`] as a [`bool`] value. - /// - /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is /// needed. - /// + /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is /// used for implementing [`GraphQLValue`] for [`bool`] for all possible [`ScalarValue`]s. /// - /// Implementations should not implement this method, but rather implement the + /// Implementations should not implement this method, but rather implement the /// [`TryScalarValueTo`] conversions for all the supported boolean types. #[must_use] fn try_to_bool(&self) -> Option { @@ -236,16 +239,16 @@ pub trait ScalarValue: /// Tries to represent this [`ScalarValue`] as an [`i32`] value. /// - /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is /// needed. /// /// # Implementation - /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is /// used for implementing [`GraphQLValue`] for [`i32`] for all possible [`ScalarValue`]s. /// - /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`] conversions for all the supported integer types with 32 bit or + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`] conversions for all the supported integer types with 32 bit or /// less to an integer, if requested. #[must_use] fn try_to_int(&self) -> Option { @@ -254,16 +257,16 @@ pub trait ScalarValue: /// Tries to represent this [`ScalarValue`] as a [`f64`] value. /// - /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] is /// needed. - /// + /// /// # Implementation - /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is /// used for implementing [`GraphQLValue`] for [`f64`] for all possible [`ScalarValue`]s. /// - /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`] conversions for all the supported integer types with 64 bit and + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`] conversions for all the supported integer types with 64 bit and /// all floating point values with 64 bit or less to a float, if requested. #[must_use] fn try_to_float(&self) -> Option { @@ -271,19 +274,19 @@ pub trait ScalarValue: } /// Tries to represent this [`ScalarValue`] as a [`String`] value. - /// - /// Allocates every time is called. For read-only and non-owning use of the underlying + /// + /// Allocates every time is called. For read-only and non-owning use of the underlying /// [`String`] value, consider using the [`ScalarValue::try_as_str()`] method. /// - /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] + /// Use the [`ScalarValue::try_to::()`] method in case the [`TryScalarValueTo::Error`] /// is needed. /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is + /// This method is an ergonomic alias for the [`TryScalarValueTo`] conversion, which is /// used for implementing [`GraphQLValue`] for [`String`] for all possible [`ScalarValue`]s. /// - /// Implementations should not implement this method, but rather implement the + /// Implementations should not implement this method, but rather implement the /// [`TryScalarValueTo`] conversions for all the supported string types, if requested. #[must_use] fn try_to_string(&self) -> Option { @@ -292,16 +295,16 @@ pub trait ScalarValue: /// Tries to convert this [`ScalarValue`] into a [`String`] value. /// - /// Similar to the [`ScalarValue::try_to_string()`] method, but takes ownership, so allows to + /// Similar to the [`ScalarValue::try_to_string()`] method, but takes ownership, so allows to /// omit redundant [`Clone`]ing. /// /// Use the [`TryInto`] conversion in case the [`TryInto::Error`] is needed. - /// + /// /// # Implementation /// /// This method is an ergonomic alias for the [`TryInto`] conversion. /// - /// Implementations should not implement this method, but rather implement the + /// Implementations should not implement this method, but rather implement the /// [`TryInto`] conversion for all the supported string types, if requested. #[must_use] fn try_into_string(self) -> Option { @@ -310,17 +313,17 @@ pub trait ScalarValue: /// Tries to represent this [`ScalarValue`] as a [`str`] value. /// - /// Use the [`ScalarValue::try_to::<&str>()`] method in case the [`TryScalarValueTo::Error`] + /// Use the [`ScalarValue::try_to::<&str>()`] method in case the [`TryScalarValueTo::Error`] /// is needed. /// /// # Implementation /// - /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<&`[`str`]`>` conversion, - /// which is used for implementing [`GraphQLValue`] for [`String`] for all possible + /// This method is an ergonomic alias for the [`TryScalarValueTo`]`<&`[`str`]`>` conversion, + /// which is used for implementing [`GraphQLValue`] for [`String`] for all possible /// [`ScalarValue`]s. /// - /// Implementations should not implement this method, but rather implement the - /// [`TryScalarValueTo`]`<&`[`str`]`>` conversions for all the supported string types, if + /// Implementations should not implement this method, but rather implement the + /// [`TryScalarValueTo`]`<&`[`str`]`>` conversions for all the supported string types, if /// requested. #[must_use] fn try_as_str(&self) -> Option<&str> { @@ -329,10 +332,10 @@ pub trait ScalarValue: /// Converts this [`ScalarValue`] into another one via [`i32`], [`f64`], [`bool`] or [`String`] /// conversion. - /// + /// /// # Panics - /// - /// If this [`ScalarValue`] doesn't represent at least one of [`i32`], [`f64`], [`bool`] or + /// + /// If this [`ScalarValue`] doesn't represent at least one of [`i32`], [`f64`], [`bool`] or /// [`String`]. #[must_use] fn into_another(self) -> S { diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 5718edee7..5ca27cedf 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -771,11 +771,14 @@ impl Methods { .. } => { quote! { + use ::juniper::macros::helper::ToResultCall as _; + let input = ::juniper::InputValue::as_scalar(input) .ok_or_else(|| format!("Expected GraphQL scalar, found: {input}"))?; let input = ::juniper::TryScalarValueTo::try_scalar_value_to(input) .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error)?; - #from_input(input) + let func: fn(_) -> _ = #from_input; + (&&func).__to_result_call(input) } } Self::Delegated { field, .. } => { diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index 278087135..de226a1f3 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -343,12 +343,12 @@ impl Definition { let var_ident = &var.ident; let field = Field::try_from(var.fields.clone()).unwrap(); let var_pattern = field.match_arg(); - - quote! { + + quote! { Self::#var_ident #var_pattern => ::juniper::AnyExt::is::<__T>(v), } }); - + quote! { fn is_type<__T: ::core::any::Any + ?::core::marker::Sized>(&self) -> bool { match self { diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index 773f281bc..b3626d54a 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -4,12 +4,12 @@ pub mod common; -use std::{fmt, convert::Infallible}; +use std::{convert::Infallible, fmt}; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, execute, graphql_object, - graphql_scalar, graphql_value, graphql_vars, Raw, + ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, execute, + graphql_object, graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index a0397af5b..8a76cbaa8 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -12,7 +12,9 @@ use self::common::hygiene::*; mod trivial { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] + #[derive( + Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + )] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -53,7 +55,9 @@ mod trivial { mod named_fields { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] + #[derive( + Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + )] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -94,7 +98,9 @@ mod named_fields { mod custom_fn { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] + #[derive( + Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + )] #[serde(untagged)] pub enum CustomScalarValue { #[value(as_float, as_int)] @@ -139,7 +145,9 @@ mod custom_fn { mod allow_missing_attributes { use super::*; - #[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] + #[derive( + Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + )] #[serde(untagged)] #[value(allow_missing_attributes)] pub enum CustomScalarValue { From 0d3217ebe3a4c016f89ce3f1d70b5921de980244 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 17:54:51 +0200 Subject: [PATCH 09/22] Apply return-type polymorphism --- juniper/src/executor_tests/variables.rs | 13 ++--- juniper/src/integrations/bigdecimal.rs | 13 ++--- juniper/src/integrations/rust_decimal.rs | 12 +---- juniper/src/macros/helper/mod.rs | 5 +- juniper/src/types/scalars.rs | 38 ++++---------- juniper/src/value/scalar.rs | 12 ++--- .../scalar/type_alias/attr_invalid_url.rs | 6 +-- .../tests/codegen_scalar_attr_derive_input.rs | 49 +++++++++---------- .../tests/codegen_scalar_attr_type_alias.rs | 45 ++++++----------- .../tests/codegen_scalar_derive.rs | 44 ++++++++--------- tests/integration/tests/custom_scalar.rs | 4 +- 11 files changed, 91 insertions(+), 150 deletions(-) diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 4775db290..387436ad0 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,5 +1,5 @@ use crate::{ - GraphQLInputObject, GraphQLScalar, InputValue, ScalarValue, Value, + GraphQLInputObject, GraphQLScalar, ScalarValue, Value, executor::Variables, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, @@ -18,11 +18,12 @@ impl TestComplexScalar { graphql_value!("SerializedValue") } - fn from_input(s: &impl ScalarValue) -> Result { - s.try_as_str() - .filter(|s| *s == "SerializedValue") - .map(|_| Self) - .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {s}"#)) + fn from_input(s: &str) -> Result> { + if s == "SerializedValue" { + Ok(Self) + } else { + Err(format!(r#"Expected "SerializedValue" string, found: "{s}""#).into()) + } } } diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 0e32fd3d0..60288e334 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{Raw, ScalarValue, Value, WrongInputScalarTypeError, graphql_scalar}; +use crate::{Raw, ScalarValue, Value, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -53,15 +53,8 @@ mod bigdecimal_scalar { BigDecimal::from_str(buf.format(f)) .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into()) } else { - v.try_as_str() - .ok_or_else(|| { - WrongInputScalarTypeError { - type_name: arcstr::literal!("String"), - input: &**v, - } - .to_string() - .into() - }) + v.try_to::<&str>() + .map_err(|e| e.to_string().into()) .and_then(|s| { BigDecimal::from_str(s).map_err(|e| { format!("Failed to parse `BigDecimal` from `String`: {e}").into() diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index e23e2d1bd..c82ac4060 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -35,7 +35,6 @@ type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { use super::*; - use crate::WrongInputScalarTypeError; pub(super) fn to_output(v: &Decimal) -> Value { Value::scalar(v.to_string()) @@ -48,15 +47,8 @@ mod rust_decimal_scalar { Decimal::try_from(f) .map_err(|e| format!("Failed to parse `Decimal` from `Float`: {e}").into()) } else { - v.try_as_str() - .ok_or_else(|| { - WrongInputScalarTypeError { - type_name: arcstr::literal!("String"), - input: &**v, - } - .to_string() - .into() - }) + v.try_to::<&str>() + .map_err(|e| e.to_string().into()) .and_then(|s| { Decimal::from_str(s) .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}").into()) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index b2183d4f4..d3528244b 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -6,10 +6,7 @@ use std::{convert::Infallible, fmt}; use futures::future::{self, BoxFuture}; -use crate::{ - DefaultScalarValue, FieldError, FieldResult, GraphQLScalar, InputValue, IntoFieldError, - ScalarValue, -}; +use crate::FieldError; /// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type /// from a [`Result`]. diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 594a15120..a0e700d50 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -1,4 +1,4 @@ -use std::{char, convert::Infallible, marker::PhantomData, rc::Rc, thread::JoinHandle}; +use std::{char, convert::identity, marker::PhantomData, rc::Rc, thread::JoinHandle}; use derive_more::with_trait::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; @@ -56,7 +56,7 @@ impl ID { } #[graphql_scalar] -#[graphql(with = impl_string_scalar)] +#[graphql(with = impl_string_scalar, from_input_with = identity::)] type String = std::string::String; mod impl_string_scalar { @@ -66,10 +66,6 @@ mod impl_string_scalar { Value::scalar(v.to_owned()) } - pub(super) fn from_input(s: String) -> Result { - Ok(s) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); @@ -179,8 +175,6 @@ where type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { - use std::convert::Infallible; - use super::ArcStr; use crate::{IntoValue as _, ScalarValue, Value}; @@ -188,8 +182,8 @@ mod impl_arcstr_scalar { v.into_value() } - pub(super) fn from_input(s: &str) -> Result { - Ok(s.into()) + pub(super) fn from_input(s: &str) -> ArcStr { + s.into() } } @@ -198,8 +192,6 @@ mod impl_arcstr_scalar { type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { - use std::convert::Infallible; - use super::CompactString; use crate::{IntoValue as _, ScalarValue, Value}; @@ -207,8 +199,8 @@ mod impl_compactstring_scalar { v.into_value() } - pub(super) fn from_input(s: &str) -> Result { - Ok(s.into()) + pub(super) fn from_input(s: &str) -> CompactString { + s.into() } } @@ -283,7 +275,7 @@ where } #[graphql_scalar] -#[graphql(with = impl_boolean_scalar)] +#[graphql(with = impl_boolean_scalar, from_input_with = identity::)] type Boolean = bool; mod impl_boolean_scalar { @@ -293,10 +285,6 @@ mod impl_boolean_scalar { Value::scalar(*v) } - pub(super) fn from_input(b: Boolean) -> Result { - Ok(b) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { // `Boolean`s are parsed separately, they shouldn't reach this code path. Err(ParseError::unexpected_token(Token::Scalar(value))) @@ -304,7 +292,7 @@ mod impl_boolean_scalar { } #[graphql_scalar] -#[graphql(with = impl_int_scalar)] +#[graphql(with = impl_int_scalar, from_input_with = identity::)] type Int = i32; mod impl_int_scalar { @@ -314,10 +302,6 @@ mod impl_int_scalar { Value::scalar(*v) } - pub(super) fn from_input(i: Int) -> Result { - Ok(i) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::Int(v) = value { v.parse() @@ -330,7 +314,7 @@ mod impl_int_scalar { } #[graphql_scalar] -#[graphql(with = impl_float_scalar)] +#[graphql(with = impl_float_scalar, from_input_with = identity::)] type Float = f64; mod impl_float_scalar { @@ -340,10 +324,6 @@ mod impl_float_scalar { Value::scalar(*v) } - pub(super) fn from_input(f: Float) -> Result { - Ok(f) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { match value { ScalarToken::Int(v) => v diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index be516f0e3..7fad5a679 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -171,12 +171,12 @@ pub trait ScalarValue: + From + From + From - + for<'a> TryScalarValueTo<'a, bool, Error: IntoFieldError> - + for<'a> TryScalarValueTo<'a, i32, Error: IntoFieldError> - + for<'a> TryScalarValueTo<'a, f64, Error: IntoFieldError> - + for<'a> TryScalarValueTo<'a, String, Error: IntoFieldError> - + for<'a> TryScalarValueTo<'a, &'a str, Error: IntoFieldError> - + for<'a> TryScalarValueTo<'a, &'a Self, Error: IntoFieldError> + + for<'a> TryScalarValueTo<'a, bool, Error: Display + IntoFieldError> + + for<'a> TryScalarValueTo<'a, i32, Error: Display + IntoFieldError> + + for<'a> TryScalarValueTo<'a, f64, Error: Display + IntoFieldError> + + for<'a> TryScalarValueTo<'a, String, Error: Display + IntoFieldError> + + for<'a> TryScalarValueTo<'a, &'a str, Error: Display + IntoFieldError> + + for<'a> TryScalarValueTo<'a, &'a Self, Error: Display + IntoFieldError> + TryInto + 'static { diff --git a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs index 812f6756e..8edad9d6e 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs +++ b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs @@ -1,5 +1,3 @@ -use std::convert::Infallible; - use juniper::{graphql_scalar, ScalarValue, Value}; struct ScalarSpecifiedByUrl; @@ -18,8 +16,8 @@ mod scalar { Value::scalar(0) } - pub(super) fn from_input(_: &impl ScalarValue) -> Result { - Ok(ScalarSpecifiedByUrl) + pub(super) fn from_input(_: &Raw) -> ScalarSpecifiedByUrl { + ScalarSpecifiedByUrl } } diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index b3626d54a..b0e6f8804 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -4,7 +4,7 @@ pub mod common; -use std::{convert::Infallible, fmt}; +use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ @@ -31,8 +31,8 @@ mod trivial { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -226,21 +226,18 @@ mod transparent_with_resolver { mod all_custom_resolvers { use super::*; - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, - from_input_with = from_input, + from_input_with = Counter, )] - #[graphql_scalar(parse_token_with = parse_token)] + #[graphql(parse_token_with = parse_token)] struct Counter(i32); fn to_output(v: &Counter) -> Value { Value::scalar(v.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Counter(i)) - } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -310,8 +307,8 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -384,8 +381,8 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -588,8 +585,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -743,8 +740,8 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -817,8 +814,8 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -891,8 +888,8 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -965,8 +962,8 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -1038,8 +1035,8 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 5fcecf3f9..8d32d3f99 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -2,7 +2,7 @@ pub mod common; -use std::{convert::Infallible, fmt}; +use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ @@ -20,13 +20,12 @@ use self::common::hygiene::*; mod all_custom_resolvers { use super::*; - use std::convert::Infallible; struct CustomCounter(i32); #[graphql_scalar( to_output_with = to_output, - from_input_with = from_input, + from_input_with = CustomCounter, )] #[graphql_scalar( parse_token_with = parse_token, @@ -37,10 +36,6 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) - } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -107,7 +102,7 @@ mod explicit_name { #[graphql_scalar( name = "Counter", to_output_with = to_output, - from_input_with = from_input, + from_input_with = CustomCounter, parse_token_with = parse_token, )] type CounterScalar = CustomCounter; @@ -116,10 +111,6 @@ mod explicit_name { Value::scalar(v.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) - } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -205,7 +196,7 @@ mod delegated_parse_token { #[graphql_scalar( to_output_with = to_output, - from_input_with = from_input, + from_input_with = CustomCounter, parse_token(i32), )] type Counter = CustomCounter; @@ -214,10 +205,6 @@ mod delegated_parse_token { Value::scalar(v.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) - } - struct QueryRoot; #[graphql_object] @@ -425,8 +412,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -586,8 +573,8 @@ mod description_from_doc_comment { Value::scalar(v.0) } - pub(super) fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) + pub(super) fn from_input(i: i32) -> Counter { + CustomCounter(i) } } @@ -668,8 +655,8 @@ mod description_from_attribute { Value::scalar(v.0) } - pub(super) fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) + pub(super) fn from_input(i: i32) -> Counter { + CustomCounter(i) } } @@ -750,8 +737,8 @@ mod custom_scalar { Value::scalar(v.0) } - pub(super) fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) + pub(super) fn from_input(i: i32) -> Counter { + CustomCounter(i) } } @@ -832,8 +819,8 @@ mod generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) + pub(super) fn from_input(i: i32) -> Counter { + CustomCounter(i) } } @@ -914,8 +901,8 @@ mod bounded_generic_scalar { Value::scalar(v.0) } - pub(super) fn from_input(i: i32) -> prelude::Result { - Ok(CustomCounter(i)) + pub(super) fn from_input(i: i32) -> Counter { + CustomCounter(i) } } diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index caf34e979..6b0ea1821 100644 --- a/tests/integration/tests/codegen_scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -2,7 +2,7 @@ pub mod common; -use std::{convert::Infallible, fmt}; +use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ @@ -29,8 +29,8 @@ mod trivial { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -226,7 +226,7 @@ mod all_custom_resolvers { #[derive(GraphQLScalar)] #[graphql( to_output_with = to_output, - from_input_with = from_input, + from_input_with = Counter, )] #[graphql(parse_token_with = parse_token)] struct Counter(i32); @@ -235,10 +235,6 @@ mod all_custom_resolvers { Value::scalar(v.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Counter(i)) - } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { >::from_str(value) } @@ -309,8 +305,8 @@ mod explicit_name { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -384,8 +380,8 @@ mod delegated_parse_token { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -591,8 +587,8 @@ mod with_self { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -748,8 +744,8 @@ mod description_from_doc_comment { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -823,8 +819,8 @@ mod description_from_attribute { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -898,8 +894,8 @@ mod custom_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -973,8 +969,8 @@ mod generic_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } @@ -1047,8 +1043,8 @@ mod bounded_generic_scalar { Value::scalar(self.0) } - fn from_input(i: i32) -> prelude::Result { - Ok(Self(i)) + fn from_input(i: i32) -> Self { + Self(i) } } diff --git a/tests/integration/tests/custom_scalar.rs b/tests/integration/tests/custom_scalar.rs index 1107a674e..1f6b7c4d6 100644 --- a/tests/integration/tests/custom_scalar.rs +++ b/tests/integration/tests/custom_scalar.rs @@ -22,11 +22,11 @@ mod long { Value::scalar(*v) } - pub(super) fn from_input(s: &MyScalarValue) -> Result { + pub(super) fn from_input(s: &MyScalarValue) -> Result> { if let MyScalarValue::Long(i) = s { Ok(*i) } else { - Err(format!("Expected `MyScalarValue::Long`, found: {s}")) + Err(format!("Expected `MyScalarValue::Long`, found: {s}").into()) } } From 78075a460c0235359dffa42dcb1e943470a34dae Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 18:05:53 +0200 Subject: [PATCH 10/22] Refactor attrs in tests --- .../tests/codegen_scalar_attr_derive_input.rs | 39 ++++++++++++------- .../tests/codegen_scalar_attr_type_alias.rs | 38 +++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index b0e6f8804..0f44a6d8f 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -97,7 +97,8 @@ mod trivial { mod transparent { use super::*; - #[graphql_scalar(transparent)] + #[graphql_scalar] + #[graphql(transparent)] struct Counter(i32); struct QueryRoot; @@ -157,7 +158,8 @@ mod transparent { mod transparent_with_resolver { use super::*; - #[graphql_scalar( + #[graphql_scalar] + #[graphql( transparent, to_output_with = Self::to_output, )] @@ -299,7 +301,8 @@ mod all_custom_resolvers { mod explicit_name { use super::*; - #[graphql_scalar(name = "Counter")] + #[graphql_scalar] + #[graphql(name = "Counter")] struct CustomCounter(i32); impl CustomCounter { @@ -373,7 +376,8 @@ mod explicit_name { mod delegated_parse_token { use super::*; - #[graphql_scalar(parse_token(i32))] + #[graphql_scalar] + #[graphql(parse_token(i32))] struct Counter(i32); impl Counter { @@ -443,7 +447,8 @@ mod delegated_parse_token { mod multiple_delegated_parse_token { use super::*; - #[graphql_scalar(parse_token(prelude::String, i32))] + #[graphql_scalar] + #[graphql(parse_token(prelude::String, i32))] enum StringOrInt { String(prelude::String), Int(i32), @@ -502,7 +507,8 @@ mod multiple_delegated_parse_token { mod where_attribute { use super::*; - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, from_input_with = from_input, parse_token(prelude::String), @@ -577,7 +583,8 @@ mod where_attribute { mod with_self { use super::*; - #[graphql_scalar(with = Self)] + #[graphql_scalar] + #[graphql(with = Self)] struct Counter(i32); impl Counter { @@ -651,7 +658,8 @@ mod with_self { mod with_module { use super::*; - #[graphql_scalar( + #[graphql_scalar] + #[graphql( with = custom_date_time, parse_token(prelude::String), where(Tz: From, Tz::Offset: fmt::Display), @@ -732,7 +740,8 @@ mod description_from_doc_comment { use super::*; /// Description - #[graphql_scalar(parse_token(i32))] + #[graphql_scalar] + #[graphql(parse_token(i32))] struct Counter(i32); impl Counter { @@ -806,7 +815,8 @@ mod description_from_attribute { use super::*; /// Doc comment - #[graphql_scalar(description = "Description from attribute", parse_token(i32))] + #[graphql_scalar] + #[graphql(description = "Description from attribute", parse_token(i32))] struct Counter(i32); impl Counter { @@ -880,7 +890,8 @@ mod custom_scalar { use super::*; /// Description - #[graphql_scalar(scalar = MyScalarValue, parse_token(i32))] + #[graphql_scalar] + #[graphql(scalar = MyScalarValue, parse_token(i32))] struct Counter(i32); impl Counter { @@ -954,7 +965,8 @@ mod generic_scalar { use super::*; /// Description - #[graphql_scalar(scalar = S: ScalarValue, parse_token(i32))] + #[graphql_scalar] + #[graphql(scalar = S: ScalarValue, parse_token(i32))] struct Counter(i32); impl Counter { @@ -1027,7 +1039,8 @@ mod generic_scalar { mod bounded_generic_scalar { use super::*; - #[graphql_scalar(scalar = S: ScalarValue + prelude::Clone, parse_token(i32))] + #[graphql_scalar] + #[graphql(scalar = S: ScalarValue + prelude::Clone, parse_token(i32))] struct Counter(i32); impl Counter { diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 8d32d3f99..585d5bdae 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -23,11 +23,12 @@ mod all_custom_resolvers { struct CustomCounter(i32); - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, from_input_with = CustomCounter, )] - #[graphql_scalar( + #[graphql( parse_token_with = parse_token, )] type Counter = CustomCounter; @@ -99,7 +100,8 @@ mod explicit_name { struct CustomCounter(i32); - #[graphql_scalar( + #[graphql_scalar] + #[graphql( name = "Counter", to_output_with = to_output, from_input_with = CustomCounter, @@ -194,7 +196,8 @@ mod delegated_parse_token { struct CustomCounter(i32); - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, from_input_with = CustomCounter, parse_token(i32), @@ -267,7 +270,8 @@ mod multiple_delegated_parse_token { Int(i32), } - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, from_input_with = from_input, parse_token(prelude::String, i32), @@ -327,7 +331,8 @@ mod where_attribute { struct CustomDateTimeScalar(DateTime); - #[graphql_scalar( + #[graphql_scalar] + #[graphql( to_output_with = to_output, from_input_with = from_input, parse_token(prelude::String), @@ -404,7 +409,8 @@ mod with_self { struct CustomCounter(i32); - #[graphql_scalar(with = Self)] + #[graphql_scalar] + #[graphql(with = Self)] type Counter = CustomCounter; impl Counter { @@ -480,7 +486,8 @@ mod with_module { struct CustomDateTimeScalar(DateTime); - #[graphql_scalar( + #[graphql_scalar] + #[graphql( with = custom_date_time, parse_token(prelude::String), where(Tz: From + TimeZone, Tz::Offset: fmt::Display), @@ -563,7 +570,8 @@ mod description_from_doc_comment { struct CustomCounter(i32); /// Description - #[graphql_scalar(with = counter, parse_token(i32))] + #[graphql_scalar] + #[graphql(with = counter, parse_token(i32))] type Counter = CustomCounter; mod counter { @@ -641,7 +649,8 @@ mod description_from_attribute { struct CustomCounter(i32); /// Doc comment - #[graphql_scalar( + #[graphql_scalar] + #[graphql( description = "Description from attribute", with = counter, parse_token(i32), @@ -723,7 +732,8 @@ mod custom_scalar { struct CustomCounter(i32); /// Description - #[graphql_scalar( + #[graphql_scalar] + #[graphql( scalar = MyScalarValue, with = counter, parse_token(i32), @@ -805,7 +815,8 @@ mod generic_scalar { struct CustomCounter(i32); /// Description - #[graphql_scalar( + #[graphql_scalar] + #[graphql( scalar = S: ScalarValue, with = counter, parse_token(i32), @@ -887,7 +898,8 @@ mod bounded_generic_scalar { struct CustomCounter(i32); /// Description - #[graphql_scalar( + #[graphql_scalar] + #[graphql( scalar = S: ScalarValue + prelude::Clone, with = counter, parse_token(i32), From 4cd165b2026f34a12fb2c8d3afe9ad35eac78b28 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 18:18:34 +0200 Subject: [PATCH 11/22] Rename `Raw` in `Scalar` --- juniper/src/integrations/bigdecimal.rs | 4 +-- juniper/src/integrations/rust_decimal.rs | 4 +-- juniper/src/lib.rs | 10 +++----- juniper/src/types/scalars.rs | 4 +-- juniper/src/value/mod.rs | 2 +- juniper/src/value/scalar.rs | 25 +++++++++++++------ .../scalar/type_alias/attr_invalid_url.rs | 7 +++--- .../tests/codegen_scalar_attr_derive_input.rs | 6 ++--- .../tests/codegen_scalar_attr_type_alias.rs | 4 +-- .../tests/codegen_scalar_derive.rs | 4 +-- 10 files changed, 39 insertions(+), 31 deletions(-) diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 60288e334..1defe431b 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{Raw, ScalarValue, Value, graphql_scalar}; +use crate::{Scalar, ScalarValue, Value, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -43,7 +43,7 @@ mod bigdecimal_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &Raw) -> Result> { + pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { Ok(BigDecimal::from(i)) } else if let Some(f) = v.try_to_float() { diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index c82ac4060..c8952e47a 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -10,7 +10,7 @@ use std::str::FromStr as _; -use crate::{Raw, ScalarValue, Value, graphql_scalar}; +use crate::{Scalar, ScalarValue, Value, graphql_scalar}; /// 128 bit representation of a fixed-precision decimal number. /// @@ -40,7 +40,7 @@ mod rust_decimal_scalar { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &Raw) -> Result> { + pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { Ok(Decimal::from(i)) } else if let Some(f) = v.try_to_float() { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index ac800a2f7..99fef2dc2 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -56,9 +56,9 @@ pub mod tests; #[cfg(test)] mod executor_tests; -use derive_more::with_trait::{Deref, Display, From}; +use derive_more::with_trait::{Display, From}; use itertools::Itertools as _; -use ref_cast::RefCast; + // Needs to be public because macros use it. pub use crate::util::to_camel_case; @@ -103,7 +103,7 @@ pub use crate::{ }, validation::RuleError, value::{ - AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue, + AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue, Scalar, ScalarValue, ScalarValueFmt, TryScalarValueTo, Value, WrongInputScalarTypeError, }, }; @@ -319,7 +319,3 @@ where context, ) } - -#[derive(Debug, Deref, Display, RefCast)] -#[repr(transparent)] -pub struct Raw(T); diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index a0e700d50..caf88c39a 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -4,7 +4,7 @@ use derive_more::with_trait::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; use crate::{ - GraphQLScalar, Raw, + GraphQLScalar, Scalar, ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, graphql_scalar, @@ -36,7 +36,7 @@ impl ID { Value::scalar(self.0.clone().into_string()) } - fn from_input(v: &Raw) -> Result> { + fn from_input(v: &Scalar) -> Result> { v.try_to_string() .or_else(|| v.try_to_int().as_ref().map(ToString::to_string)) .map(|s| Self(s.into())) diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 23fb27986..bd88e22c5 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -9,7 +9,7 @@ use compact_str::CompactString; pub use self::{ object::Object, scalar::{ - AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue, + AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, Scalar, ScalarValue, ScalarValueFmt, TryScalarValueTo, WrongInputScalarTypeError, }, }; diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 7fad5a679..6aab1de57 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -1,8 +1,8 @@ use std::convert::Infallible; use arcstr::ArcStr; -use derive_more::with_trait::{Display, Error, From, TryInto}; -use ref_cast::RefCast as _; +use derive_more::with_trait::{Deref, Display, Error, From, TryInto}; +use ref_cast::RefCast; use serde::{Serialize, de::DeserializeOwned}; use std::{ any::{Any, TypeId}, @@ -11,11 +11,11 @@ use std::{ }; use crate::{ - FieldError, IntoFieldError, Raw, + FieldError, IntoFieldError, parser::{ParseError, ScalarToken}, }; #[cfg(doc)] -use crate::{GraphQLValue, Value}; +use crate::{GraphQLScalar, GraphQLValue, Value}; pub use juniper_codegen::ScalarValue; @@ -382,11 +382,11 @@ impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me S> for S { } } -impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me Raw> for S { +impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me Scalar> for S { type Error = Infallible; - fn try_scalar_value_to(&'me self) -> Result<&'me Raw, Self::Error> { - Ok(Raw::ref_cast(self)) + fn try_scalar_value_to(&'me self) -> Result<&'me Scalar, Self::Error> { + Ok(Scalar::ref_cast(self)) } } @@ -419,6 +419,17 @@ impl<'a, S: ScalarValue> Display for ScalarValueFmt<'a, S> { } } +/// Transparent wrapper over a value, indicating it being a [`ScalarValue`]. +/// +/// Used in [`GraphQLScalar`] definitions to distinguish a concrete type for a generic +/// [`ScalarValue`], since Rust type inference fail do so for a generic value directly in macro +/// expansions. +#[derive(Debug, Deref, Display, RefCast)] +#[display("{}", ScalarValueFmt(_0))] +#[display(bound(T: ScalarValue))] +#[repr(transparent)] +pub struct Scalar(T); + /// Extension of [`Any`] for using its methods directly on the value without `dyn`. pub trait AnyExt: Any { /// Returns `true` if the this type is the same as `T`. diff --git a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs index 8edad9d6e..8e20a3286 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs +++ b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs @@ -1,8 +1,9 @@ -use juniper::{graphql_scalar, ScalarValue, Value}; +use juniper::{graphql_scalar, Scalar, ScalarValue, Value}; struct ScalarSpecifiedByUrl; -#[graphql_scalar( +#[graphql_scalar] +#[graphql( specified_by_url = "not an url", with = scalar, parse_token(i32), @@ -16,7 +17,7 @@ mod scalar { Value::scalar(0) } - pub(super) fn from_input(_: &Raw) -> ScalarSpecifiedByUrl { + pub(super) fn from_input(_: &Scalar) -> ScalarSpecifiedByUrl { ScalarSpecifiedByUrl } } diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index 0f44a6d8f..b1422e656 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -8,7 +8,7 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, execute, + ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, }; @@ -159,7 +159,7 @@ mod transparent_with_resolver { use super::*; #[graphql_scalar] - #[graphql( + #[graphql_scalar( transparent, to_output_with = Self::to_output, )] @@ -462,7 +462,7 @@ mod multiple_delegated_parse_token { } } - fn from_input(v: &Raw) -> prelude::Result> { + fn from_input(v: &Scalar) -> prelude::Result> { v.try_to_string() .map(Self::String) .or_else(|| v.try_to_int().map(Self::Int)) diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 585d5bdae..a29716a67 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -6,7 +6,7 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, execute, + ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, }; @@ -285,7 +285,7 @@ mod multiple_delegated_parse_token { } } - fn from_input(v: &Raw) -> prelude::Result> { + fn from_input(v: &Scalar) -> prelude::Result> { v.try_to_string() .map(StringOrInt::String) .or_else(|| v.try_to_int().map(StringOrInt::Int)) diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index 6b0ea1821..dfd0a5b96 100644 --- a/tests/integration/tests/codegen_scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -6,7 +6,7 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - GraphQLScalar, ParseScalarResult, ParseScalarValue, Raw, ScalarToken, ScalarValue, Value, + GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, execute, graphql_object, graphql_value, graphql_vars, }; @@ -457,7 +457,7 @@ mod multiple_delegated_parse_token { } } - fn from_input(v: &Raw) -> prelude::Result> { + fn from_input(v: &Scalar) -> prelude::Result> { v.try_to_string() .map(Self::String) .or_else(|| v.try_to_int().map(Self::Int)) From a9eacce40039bf5f9873424ccaf364f406a79e43 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 18:22:12 +0200 Subject: [PATCH 12/22] Polish `ScalarValueFmt` --- juniper/src/lib.rs | 2 +- juniper/src/value/mod.rs | 3 ++- juniper/src/value/scalar.rs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 99fef2dc2..151e0814a 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue, Scalar, - ScalarValue, ScalarValueFmt, TryScalarValueTo, Value, WrongInputScalarTypeError, + ScalarValue, TryScalarValueTo, Value, WrongInputScalarTypeError, }, }; diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index bd88e22c5..8d949e7e6 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -6,11 +6,12 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use arcstr::ArcStr; use compact_str::CompactString; +pub(crate) use self::scalar::ScalarValueFmt; pub use self::{ object::Object, scalar::{ AnyExt, DefaultScalarValue, ParseScalarResult, ParseScalarValue, Scalar, ScalarValue, - ScalarValueFmt, TryScalarValueTo, WrongInputScalarTypeError, + TryScalarValueTo, WrongInputScalarTypeError, }, }; use crate::{ diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 6aab1de57..a3916f60d 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -407,7 +407,8 @@ impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> } } -pub struct ScalarValueFmt<'a, S: ScalarValue>(pub &'a S); +/// [`Display`]-formatter for a [`ScalarValue`] to render as a [`Value`]. +pub(crate) struct ScalarValueFmt<'a, S: ScalarValue>(pub &'a S); impl<'a, S: ScalarValue> Display for ScalarValueFmt<'a, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From b03a7e0b9b39a48d4fc512a187f1d26b0320e9bf Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 18:40:10 +0200 Subject: [PATCH 13/22] Polish `TryScalarValueTo` --- juniper/src/value/scalar.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index a3916f60d..0e7aea8bc 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -368,13 +368,29 @@ pub trait ScalarValue: } } +/// Fallible representation of a [`ScalarValue`] as one of the types it consists of, or derived ones +/// from them. +/// +/// # Implementation +/// +/// Implementing this trait for a type allows to specify this type directly in the `from_input()` +/// function when implementing a [`GraphQLScalar`] via [derive macro](macro@GraphQLScalar). +/// +/// `#[derive(`[`ScalarValue`](macro@crate::ScalarValue)`)]` automatically implements this trait for +/// all the required primitive types if `#[to_]` and `#[as_]` attributes are specified. pub trait TryScalarValueTo<'me, T: 'me> { + /// Error if this [`ScalarValue`] doesn't represent the expected type. type Error; + /// Tries to represent this [`ScalarValue`] as the expected type. + /// + /// # Errors + /// + /// If this [`ScalarValue`] doesn't represent the expected type. fn try_scalar_value_to(&'me self) -> Result; } -impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me S> for S { +impl<'me, S: ScalarValue> TryScalarValueTo<'me, &'me S> for S { type Error = Infallible; fn try_scalar_value_to(&'me self) -> Result<&'me S, Self::Error> { @@ -382,7 +398,7 @@ impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me S> for S { } } -impl<'me, S: ?Sized> TryScalarValueTo<'me, &'me Scalar> for S { +impl<'me, S: ScalarValue> TryScalarValueTo<'me, &'me Scalar> for S { type Error = Infallible; fn try_scalar_value_to(&'me self) -> Result<&'me Scalar, Self::Error> { @@ -427,9 +443,8 @@ impl<'a, S: ScalarValue> Display for ScalarValueFmt<'a, S> { /// expansions. #[derive(Debug, Deref, Display, RefCast)] #[display("{}", ScalarValueFmt(_0))] -#[display(bound(T: ScalarValue))] #[repr(transparent)] -pub struct Scalar(T); +pub struct Scalar(T); /// Extension of [`Any`] for using its methods directly on the value without `dyn`. pub trait AnyExt: Any { From aeeddef8138c1fec4c16c8d9ee133f112dfa88c0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Jun 2025 20:15:57 +0200 Subject: [PATCH 14/22] Update `derive(ScalarValue)` --- juniper/src/macros/helper/mod.rs | 17 +- juniper/src/value/scalar.rs | 50 ++++-- juniper_codegen/src/graphql_scalar/mod.rs | 2 +- juniper_codegen/src/lib.rs | 22 +-- juniper_codegen/src/scalar_value/mod.rs | 165 +++++++----------- .../tests/codegen_scalar_value_derive.rs | 48 +++-- tests/integration/tests/common/mod.rs | 8 +- 7 files changed, 157 insertions(+), 155 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index d3528244b..75a414aa2 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,17 +2,17 @@ pub mod subscription; -use std::{convert::Infallible, fmt}; +use std::convert::Infallible; +use derive_more::with_trait::Display; use futures::future::{self, BoxFuture}; -use crate::FieldError; +use crate::{FieldError, InputValue, ScalarValue}; -/// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type -/// from a [`Result`]. +/// This trait is used by [`graphql_scalar`] macro to retrieve [`Error`] type from a [`Result`]. /// /// [`Error`]: Result::Error -/// [`graphql_scalar!`]: macro@crate::graphql_scalar +/// [`graphql_scalar`]: macro@crate::graphql_scalar pub trait ExtractError { /// Extracted [`Error`] type of this [`Result`]. /// @@ -28,7 +28,7 @@ impl ExtractError for Result { /// which immediately resolves into [`FieldError`]. pub fn err_fut<'ok, D, Ok, S>(msg: D) -> BoxFuture<'ok, Result>> where - D: fmt::Display, + D: Display, Ok: Send + 'ok, S: Send + 'static, { @@ -54,6 +54,11 @@ where Box::pin(future::err(err_unnamed_type(name))) } +/// Error of an [`InputValue`] not representing a [`ScalarValue`], used in macro expansions. +#[derive(Display)] +#[display("Expected GraphQL scalar, found: {_0}")] +pub struct NotScalarError<'a, S: ScalarValue>(pub &'a InputValue); + /// [Autoref-based specialized][0] coercion into a [`Result`] for a function call for providing a /// return-type polymorphism in macros. /// diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 0e7aea8bc..3eaae254e 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -37,9 +37,10 @@ pub trait ParseScalarValue { /// # Deriving /// /// There is a custom derive (`#[derive(`[`ScalarValue`](macro@crate::ScalarValue)`)]`) available, -/// that implements most of the required traits automatically for an enum representing a -/// [`ScalarValue`]. However, [`Serialize`] and [`Deserialize`] implementations -/// are expected to be provided. +/// that implements most of the required [`juniper`] traits automatically for an enum representing a +/// [`ScalarValue`]. However, [`Serialize`] and [`Deserialize`] implementations are expected to be +/// provided, as we as [`Display`], [`From`] and [`TryInto`] ones (for which it's convenient to use +/// [`derive_more`]). /// /// # Example /// @@ -61,14 +62,23 @@ pub trait ParseScalarValue { /// #[serde(untagged)] /// #[value(from_displayable_with = from_compact_str)] /// enum MyScalarValue { -/// #[value(as_float, as_int)] +/// #[from] +/// #[value(to_float, to_int)] /// Int(i32), +/// +/// #[from] /// Long(i64), -/// #[value(as_float)] +/// +/// #[from] +/// #[value(to_float)] /// Float(f64), -/// #[value(as_str, as_string, into_string)] +/// +/// #[from(&str, String, CompactString)] +/// #[value(as_str, to_string)] /// String(CompactString), -/// #[value(as_bool)] +/// +/// #[from] +/// #[value(to_bool)] /// Boolean(bool), /// } /// @@ -84,10 +94,17 @@ pub trait ParseScalarValue { /// } /// } /// -/// // Macro cannot infer and generate this impl if a custom string type is used. -/// impl From for MyScalarValue { -/// fn from(value: String) -> Self { -/// Self::String(value.into()) +/// // `derive_more::TryInto` is not capable for transitive conversions yet, +/// // so this impl is manual as a custom string type is used instead of `String`. +/// impl TryFrom for String { +/// type Error = MyScalarValue; +/// +/// fn try_from(value: MyScalarValue) -> Result { +/// if let MyScalarValue::String(s) = value { +/// Ok(s.into()) +/// } else { +/// Err(value) +/// } /// } /// } /// @@ -157,6 +174,7 @@ pub trait ParseScalarValue { /// } /// ``` /// +/// [`juniper`]: crate /// [`CompactString`]: compact_str::CompactString /// [`Deserialize`]: trait@serde::Deserialize /// [`Serialize`]: trait@serde::Serialize @@ -484,7 +502,7 @@ pub enum DefaultScalarValue { /// /// [0]: https://spec.graphql.org/October2021#sec-Int #[from] - #[value(as_float, as_int)] + #[value(to_float, to_int)] Int(i32), /// [`Float` scalar][0] as a signed double‐precision fractional values as @@ -493,7 +511,7 @@ pub enum DefaultScalarValue { /// [0]: https://spec.graphql.org/October2021#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point #[from] - #[value(as_float)] + #[value(to_float)] Float(f64), /// [`String` scalar][0] as a textual data, represented as UTF‐8 character @@ -501,13 +519,13 @@ pub enum DefaultScalarValue { /// /// [0]: https://spec.graphql.org/October2021#sec-String #[from(&str, Cow<'_, str>, String)] - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(String), /// [`Boolean` scalar][0] as a `true` or `false` value. /// /// [0]: https://spec.graphql.org/October2021#sec-Boolean - #[from(bool)] - #[value(as_bool)] + #[from] + #[value(to_bool)] Boolean(bool), } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 5ca27cedf..beeecec7c 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -774,7 +774,7 @@ impl Methods { use ::juniper::macros::helper::ToResultCall as _; let input = ::juniper::InputValue::as_scalar(input) - .ok_or_else(|| format!("Expected GraphQL scalar, found: {input}"))?; + .ok_or_else(|| ::juniper::macros::helper::NotScalarError(input))?; let input = ::juniper::TryScalarValueTo::try_scalar_value_to(input) .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error)?; let func: fn(_) -> _ = #from_input; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c4ca04b10..6cd9fb8f6 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -766,13 +766,13 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// `#[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 arguments (names correspond to [`ScalarValue`] required -/// methods). +/// To derive a [`ScalarValue`] on enum you either could mark the corresponding enum variants with +/// `to_int`, `to_float`, `to_string`, `as_str` and `to_bool` attribute arguments (names correspond +/// to the similar [`ScalarValue`] methods aliasing the required [`TryScalarValueTo`] conversions), +/// or implement the required [`TryScalarValueTo`] conversions by hand. /// -/// Additional `from_displayable_with` argument could be used to specify a custom -/// implementation to override the default `ScalarValue::from_displayable()` method. +/// Additional `from_displayable_with` argument could be used to specify a custom function to +/// override the default `ScalarValue::from_displayable()` method. /// /// ```rust /// # use std::{any::Any, fmt}; @@ -784,19 +784,18 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// #[serde(untagged)] /// #[value(from_displayable_with = from_custom_str)] /// enum MyScalarValue { -/// #[value(as_float, as_int)] +/// #[value(to_float, to_int)] /// Int(i32), /// Long(i64), -/// #[value(as_float)] +/// #[value(to_float)] /// Float(f64), /// #[value( -/// into_string, /// as_str, -/// as_string = String::clone, +/// to_string = String::clone, /// )] /// // ^^^^^^^^^^^^^ custom resolvers may be provided /// String(String), -/// #[value(as_bool)] +/// #[value(to_bool)] /// Boolean(bool), /// } /// @@ -884,6 +883,7 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// ``` /// /// [`ScalarValue`]: juniper::ScalarValue +/// [`TryScalarValueTo`]: juniper::TryScalarValueTo #[proc_macro_derive(ScalarValue, attributes(value))] pub fn derive_scalar_value(input: TokenStream) -> TokenStream { diagnostic::entry_point(|| { diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index de226a1f3..3fe3ed9c1 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -25,7 +25,6 @@ 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, @@ -47,34 +46,6 @@ pub fn expand_derive(input: TokenStream) -> syn::Result { } } - 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, @@ -89,9 +60,6 @@ pub fn expand_derive(input: TokenStream) -> syn::Result { /// an enum definition. #[derive(Default)] struct Attr { - /// Allows missing [`Method`]s. - allow_missing_attrs: bool, - /// Explicitly specified function to be used as `ScalarValue::from_displayable()` /// implementation. from_displayable: Option>, @@ -103,9 +71,6 @@ impl Parse for Attr { while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { - "allow_missing_attributes" => { - out.allow_missing_attrs = true; - } "from_displayable_with" => { input.parse::()?; let scl = input.parse::()?; @@ -128,7 +93,6 @@ impl Attr { /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { - allow_missing_attrs: self.allow_missing_attrs || another.allow_missing_attrs, from_displayable: try_merge_opt!(from_displayable: self, another), }) } @@ -145,23 +109,20 @@ impl Attr { /// Possible attribute names of the `#[derive(ScalarValue)]`. #[derive(Eq, Hash, PartialEq)] enum Method { - /// `#[value(as_int)]`. - AsInt, + /// `#[value(to_int)]`. + ToInt, - /// `#[value(as_float)]`. - AsFloat, + /// `#[value(to_float)]`. + ToFloat, /// `#[value(as_str)]`. AsStr, - /// `#[value(as_string)]`. - AsString, - - /// `#[value(into_string)]`. - IntoString, + /// `#[value(to_string)]`. + ToString, - /// `#[value(as_bool)]`. - AsBool, + /// `#[value(to_bool)]`. + ToBool, } /// Available arguments behind `#[value]` attribute when generating code for an @@ -175,12 +136,11 @@ impl Parse for VariantAttr { while !input.is_empty() { let ident = input.parse::()?; let method = match ident.to_string().as_str() { - "as_int" => Method::AsInt, - "as_float" => Method::AsFloat, + "to_int" => Method::ToInt, + "to_float" => Method::ToFloat, "as_str" => Method::AsStr, - "as_string" => Method::AsString, - "into_string" => Method::IntoString, - "as_bool" => Method::AsBool, + "to_string" => Method::ToString, + "to_bool" => Method::ToBool, name => { return Err(err::unknown_arg(&ident, name)); } @@ -246,6 +206,7 @@ struct Definition { impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { self.impl_scalar_value_tokens().to_tokens(into); + self.impl_try_scalar_to_tokens().to_tokens(into); } } @@ -255,6 +216,51 @@ impl Definition { let ty_ident = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + let is_type = { + let arms = self.variants.iter().map(|var| { + let var_ident = &var.ident; + let field = Field::try_from(var.fields.clone()) + .unwrap_or_else(|_| unreachable!("already checked")); + let var_pattern = field.match_arg(); + + quote! { + Self::#var_ident #var_pattern => ::juniper::AnyExt::is::<__T>(v), + } + }); + + quote! { + fn is_type<__T: ::core::any::Any + ?::core::marker::Sized>(&self) -> bool { + match self { + #( #arms )* + } + } + } + }; + + let from_displayable = self.from_displayable.as_ref().map(|expr| { + quote! { + fn from_displayable< + __T: ::core::fmt::Display + ::core::any::Any + ?::core::marker::Sized, + >(__v: &__T) -> Self { + #expr(__v) + } + } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::ScalarValue for #ty_ident #ty_gens #where_clause { + #is_type + #from_displayable + } + } + } + + /// Returns generated code implementing `TryScalarValueTo`. + fn impl_try_scalar_to_tokens(&self) -> TokenStream { + let ty_ident = &self.ident; + let (_, ty_gens, where_clause) = self.generics.split_for_impl(); + let ref_lt = quote! { '___a }; // We don't impose additional bounds on generic parameters, because // `ScalarValue` itself has `'static` bound. @@ -262,15 +268,15 @@ impl Definition { generics.params.push(parse_quote! { #ref_lt }); let (lt_impl_gens, _, _) = generics.split_for_impl(); - let methods2 = [ + let methods = [ ( - Method::AsInt, + Method::ToInt, "Int", quote! { ::core::primitive::i32 }, quote! { ::core::convert::Into::into(*v) }, ), ( - Method::AsFloat, + Method::ToFloat, "Float", quote! { ::core::primitive::f64 }, quote! { ::core::convert::Into::into(*v) }, @@ -282,20 +288,20 @@ impl Definition { quote! { ::core::convert::AsRef::as_ref(v) }, ), ( - Method::AsString, + Method::ToString, "String", quote! { ::std::string::String }, quote! { ::std::string::ToString::to_string(v) }, ), ( - Method::AsBool, + Method::ToBool, "Bool", quote! { ::core::primitive::bool }, quote! { ::core::convert::Into::into(*v) }, ), ]; - let impls = methods2.iter().map(|(m, into_name, as_ty, default_expr)| { - let arms = self.methods.get(m).into_iter().flatten().map(|v| { + let impls = methods.iter().filter_map(|(m, into_name, as_ty, default_expr)| { + let arms = self.methods.get(m)?.iter().map(|v| { let arm_pattern = v.match_arm(); let call = if let Some(func) = &v.expr { quote! { #func(v) } @@ -306,7 +312,7 @@ impl Definition { #arm_pattern => ::core::result::Result::Ok(#call), } }); - quote! { + Some(quote! { #[automatically_derived] impl #lt_impl_gens ::juniper::TryScalarValueTo<#ref_lt, #as_ty> for #ty_ident #ty_gens #where_clause @@ -325,46 +331,9 @@ impl Definition { } } } - } + }) }); - - let from_displayable = self.from_displayable.as_ref().map(|expr| { - quote! { - fn from_displayable< - __T: ::core::fmt::Display + ::core::any::Any + ?::core::marker::Sized, - >(__v: &__T) -> Self { - #expr(__v) - } - } - }); - - let is_type = { - let arms = self.variants.iter().map(|var| { - let var_ident = &var.ident; - let field = Field::try_from(var.fields.clone()).unwrap(); - let var_pattern = field.match_arg(); - - quote! { - Self::#var_ident #var_pattern => ::juniper::AnyExt::is::<__T>(v), - } - }); - - quote! { - fn is_type<__T: ::core::any::Any + ?::core::marker::Sized>(&self) -> bool { - match self { - #( #arms )* - } - } - } - }; - quote! { - #[automatically_derived] - impl #impl_gens ::juniper::ScalarValue for #ty_ident #ty_gens #where_clause { - #is_type - #from_displayable - } - #( #impls )* } } diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index 8a76cbaa8..b77c84171 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -3,7 +3,7 @@ pub mod common; use derive_more::with_trait::{Display, From, TryInto}; -use juniper::{DefaultScalarValue, ScalarValue}; +use juniper::{DefaultScalarValue, ScalarValue, TryScalarValueTo}; use serde::{Deserialize, Serialize}; // Override `std::prelude` items to check whether macros expand hygienically. @@ -17,13 +17,13 @@ mod trivial { )] #[serde(untagged)] pub enum CustomScalarValue { - #[value(as_float, as_int)] + #[value(to_float, to_int)] Int(i32), - #[value(as_float)] + #[value(to_float)] Float(f64), - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(prelude::String), - #[value(as_bool)] + #[value(to_bool)] Boolean(bool), } @@ -60,13 +60,13 @@ mod named_fields { )] #[serde(untagged)] pub enum CustomScalarValue { - #[value(as_float, as_int)] + #[value(to_float, to_int)] Int { int: i32 }, - #[value(as_float)] + #[value(to_float)] Float(f64), - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(prelude::String), - #[value(as_bool)] + #[value(to_bool)] Boolean { v: bool }, } @@ -103,17 +103,16 @@ mod custom_fn { )] #[serde(untagged)] pub enum CustomScalarValue { - #[value(as_float, as_int)] + #[value(to_float, to_int)] Int(i32), - #[value(as_float)] + #[value(to_float)] Float(f64), #[value( as_str, - as_string = str::to_owned, - into_string = std::convert::identity, + to_string = str::to_owned, )] String(prelude::String), - #[value(as_bool)] + #[value(to_bool)] Boolean(bool), } @@ -142,24 +141,35 @@ mod custom_fn { } } -mod allow_missing_attributes { +mod missing_conv_attr { use super::*; #[derive( Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, )] #[serde(untagged)] - #[value(allow_missing_attributes)] pub enum CustomScalarValue { Int(i32), - #[value(as_float)] + #[value(to_float)] Float(f64), - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(prelude::String), - #[value(as_bool)] + #[value(to_bool)] Boolean(bool), } + impl<'me> TryScalarValueTo<'me, i32> for CustomScalarValue { + type Error = &'static str; + + fn try_scalar_value_to(&'me self) -> prelude::Result { + if let CustomScalarValue::Int(x) = self { + Ok(*x) + } else { + Err("Not `Int` definitely") + } + } + } + #[test] fn into_another() { assert!(CustomScalarValue::Int(5).try_to_int().is_none()); diff --git a/tests/integration/tests/common/mod.rs b/tests/integration/tests/common/mod.rs index bcec2112b..323a4cc07 100644 --- a/tests/integration/tests/common/mod.rs +++ b/tests/integration/tests/common/mod.rs @@ -79,14 +79,14 @@ pub mod util { #[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] #[serde(untagged)] pub enum MyScalarValue { - #[value(as_float, as_int)] + #[value(to_float, to_int)] Int(i32), Long(i64), - #[value(as_float)] + #[value(to_float)] Float(f64), - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(String), - #[value(as_bool)] + #[value(to_bool)] Boolean(bool), } From 14a85f9a3970a56d4dc39e60c1e7cba464769cc2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 17:37:23 +0200 Subject: [PATCH 15/22] Fix `juniper` crate tests --- juniper/src/tests/introspection_tests.rs | 10 +++++----- juniper/src/types/containers.rs | 8 ++++---- juniper/src/value/mod.rs | 19 ------------------- .../tests/codegen_scalar_value_derive.rs | 6 +----- 4 files changed, 10 insertions(+), 33 deletions(-) diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs index 616065a96..210c3bab0 100644 --- a/juniper/src/tests/introspection_tests.rs +++ b/juniper/src/tests/introspection_tests.rs @@ -2,16 +2,15 @@ use std::collections::HashSet; use pretty_assertions::assert_eq; +use super::schema_introspection::*; use crate::{ - graphql_vars, + ScalarValue as _, graphql_vars, introspection::IntrospectionFormat, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, }; -use super::schema_introspection::*; - #[tokio::test] async fn test_introspection_query_type_name() { let doc = r#" @@ -270,8 +269,9 @@ async fn test_introspection_possible_types() { .expect("possible type not an object") .get_field_value("name") .expect("'name' not present in type") - .as_scalar_value::() - .expect("'name' not a string") as &str + .as_scalar() + .and_then(|s| s.try_as_str()) + .expect("'name' not a string") }) .collect::>(); diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index b981711df..4ea31b990 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -668,13 +668,13 @@ mod coercion { assert_eq!( >::from_input_value(&v), Err(FromInputValueVecError::Item( - "Expected `Int`, found: null".into_field_error(), + "Expected GraphQL scalar, found: null".into_field_error(), )), ); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Item( - "Expected `Int`, found: null".into_field_error(), + "Expected GraphQL scalar, found: null".into_field_error(), )), ); assert_eq!( @@ -778,13 +778,13 @@ mod coercion { assert_eq!( <[i32; 3]>::from_input_value(&v), Err(FromInputValueArrayError::Item( - "Expected `Int`, found: null".into_field_error(), + "Expected GraphQL scalar, found: null".into_field_error(), )), ); assert_eq!( >::from_input_value(&v), Err(FromInputValueArrayError::Item( - "Expected `Int`, found: null".into_field_error(), + "Expected GraphQL scalar, found: null".into_field_error(), )), ); assert_eq!( diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 8d949e7e6..8defdb611 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -67,17 +67,6 @@ impl Value { matches!(*self, Self::Null) } - /// View the underlying scalar value if present - pub fn as_scalar_value<'a, T>(&'a self) -> Option<&'a T> - where - Option<&'a T>: From<&'a S>, - { - match self { - Self::Scalar(s) => s.into(), - _ => None, - } - } - /// View the underlying object value, if present. pub fn as_object_value(&self) -> Option<&Object> { match self { @@ -120,14 +109,6 @@ impl Value { } } - /// View the underlying string value, if present. - pub fn as_string_value<'a>(&'a self) -> Option<&'a str> - where - Option<&'a String>: From<&'a S>, - { - self.as_scalar_value::().map(String::as_str) - } - /// Maps the [`ScalarValue`] type of this [`Value`] into the specified one. pub fn map_scalar_value(self) -> Value where diff --git a/tests/integration/tests/codegen_scalar_value_derive.rs b/tests/integration/tests/codegen_scalar_value_derive.rs index b77c84171..8b71d26a8 100644 --- a/tests/integration/tests/codegen_scalar_value_derive.rs +++ b/tests/integration/tests/codegen_scalar_value_derive.rs @@ -162,11 +162,7 @@ mod missing_conv_attr { type Error = &'static str; fn try_scalar_value_to(&'me self) -> prelude::Result { - if let CustomScalarValue::Int(x) = self { - Ok(*x) - } else { - Err("Not `Int` definitely") - } + Err("Not `Int` definitely") } } From a312e57f543be77942e6783e718fe7f04c0372f7 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 17:54:58 +0200 Subject: [PATCH 16/22] Fix codegen faailure tests --- tests/codegen/Cargo.toml | 2 ++ .../fail/scalar/derive_input/attr_invalid_url.rs | 3 ++- .../scalar/derive_input/attr_invalid_url.stderr | 12 +++++++++--- .../derive_input/attr_transparent_and_with.rs | 3 ++- .../derive_input/attr_transparent_and_with.stderr | 12 +++++++++--- .../attr_transparent_multiple_named_fields.rs | 3 ++- .../attr_transparent_multiple_named_fields.stderr | 10 ++++++++-- .../attr_transparent_multiple_unnamed_fields.rs | 3 ++- ...attr_transparent_multiple_unnamed_fields.stderr | 10 ++++++++-- .../derive_input/attr_transparent_unit_struct.rs | 3 ++- .../attr_transparent_unit_struct.stderr | 10 ++++++++-- .../fail/scalar/type_alias/attr_invalid_url.rs | 2 +- .../fail/scalar/type_alias/attr_invalid_url.stderr | 10 ++++++++-- .../type_alias/attr_with_not_all_resolvers.rs | 3 ++- .../type_alias/attr_with_not_all_resolvers.stderr | 10 ++++++++-- .../fail/scalar_value/missing_attributes.rs | 9 ++++++--- .../fail/scalar_value/missing_attributes.stderr | 14 ++++++++++---- tests/codegen/src/lib.rs | 2 ++ 18 files changed, 91 insertions(+), 30 deletions(-) diff --git a/tests/codegen/Cargo.toml b/tests/codegen/Cargo.toml index 813cb0bc4..d8679ec32 100644 --- a/tests/codegen/Cargo.toml +++ b/tests/codegen/Cargo.toml @@ -8,8 +8,10 @@ publish = false rustversion = "1.0" [dev-dependencies] +derive_more = { version = "2.0", features = ["display", "from", "try_into"] } futures = "0.3" juniper = { path = "../../juniper" } +serde = { version = "1.0", features = ["derive"] } trybuild = "1.0.63" [lints.clippy] diff --git a/tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs index b28cb1fc3..59df8b501 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs +++ b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs @@ -1,6 +1,7 @@ use juniper::graphql_scalar; -#[graphql_scalar(specified_by_url = "not an url", transparent)] +#[graphql_scalar] +#[graphql(specified_by_url = "not an url", transparent)] struct ScalarSpecifiedByUrl(i32); fn main() {} diff --git a/tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr index b1bd12105..759e02342 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr +++ b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr @@ -1,5 +1,11 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/derive_input/attr_invalid_url.rs:3:37 + --> fail/scalar/derive_input/attr_invalid_url.rs:4:30 | -3 | #[graphql_scalar(specified_by_url = "not an url", transparent)] - | ^^^^^^^^^^^^ +4 | #[graphql(specified_by_url = "not an url", transparent)] + | ^^^^^^^^^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/derive_input/attr_invalid_url.rs:4:3 + | +4 | #[graphql(specified_by_url = "not an url", transparent)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs index 3b3190840..ff2174204 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs @@ -1,6 +1,7 @@ use juniper::graphql_scalar; -#[graphql_scalar(with = Self, transparent)] +#[graphql_scalar] +#[graphql(with = Self, transparent)] struct Scalar; fn main() {} diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr index ce9f808d6..c7fc6ceb5 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr @@ -1,5 +1,11 @@ error: GraphQL scalar `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. - --> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25 + --> fail/scalar/derive_input/attr_transparent_and_with.rs:4:18 | -3 | #[graphql_scalar(with = Self, transparent)] - | ^^^^ +4 | #[graphql(with = Self, transparent)] + | ^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/derive_input/attr_transparent_and_with.rs:4:3 + | +4 | #[graphql(with = Self, transparent)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs index 56658b548..3637f937e 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs @@ -1,6 +1,7 @@ use juniper::graphql_scalar; -#[graphql_scalar(transparent)] +#[graphql_scalar] +#[graphql(transparent)] struct Scalar { id: i32, another: i32, diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr index f082ef001..b24aa24f4 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr @@ -1,5 +1,11 @@ error: GraphQL scalar `transparent` attribute argument requires exactly 1 field - --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1 + --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:5:1 | -4 | struct Scalar { +5 | struct Scalar { | ^^^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:3 + | +4 | #[graphql(transparent)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs index bbf969364..3d6edbc2e 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs @@ -1,6 +1,7 @@ use juniper::graphql_scalar; -#[graphql_scalar(transparent)] +#[graphql_scalar] +#[graphql(transparent)] struct Scalar(i32, i32); fn main() {} diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr index 0c17a53c6..e115e7f00 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr @@ -1,5 +1,11 @@ error: GraphQL scalar `transparent` attribute argument requires exactly 1 field - --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1 + --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:5:1 | -4 | struct Scalar(i32, i32); +5 | struct Scalar(i32, i32); | ^^^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:3 + | +4 | #[graphql(transparent)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs index 9f8d7568d..2b21c8794 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs @@ -1,6 +1,7 @@ use juniper::graphql_scalar; -#[graphql_scalar(transparent)] +#[graphql_scalar] +#[graphql(transparent)] struct ScalarSpecifiedByUrl; fn main() {} diff --git a/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr index e83780a81..0d4e298c8 100644 --- a/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr +++ b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr @@ -1,5 +1,11 @@ error: GraphQL scalar `transparent` attribute argument requires exactly 1 field - --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1 + --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:5:1 | -4 | struct ScalarSpecifiedByUrl; +5 | struct ScalarSpecifiedByUrl; | ^^^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:3 + | +4 | #[graphql(transparent)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs index 8e20a3286..966576843 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs +++ b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs @@ -8,7 +8,7 @@ struct ScalarSpecifiedByUrl; with = scalar, parse_token(i32), )] -type Scalar = ScalarSpecifiedByUrl; +type MyScalar = ScalarSpecifiedByUrl; mod scalar { use super::*; diff --git a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr index 4ecefa21a..b364f6970 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr +++ b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr @@ -1,5 +1,11 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/type_alias/attr_invalid_url.rs:6:24 + --> fail/scalar/type_alias/attr_invalid_url.rs:7:24 | -6 | specified_by_url = "not an url", +7 | specified_by_url = "not an url", | ^^^^^^^^^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/type_alias/attr_invalid_url.rs:6:3 + | +6 | #[graphql( + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs index 3b37e214b..be209fa03 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs +++ b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs @@ -2,7 +2,8 @@ use juniper::{graphql_scalar, Value}; struct Scalar; -#[graphql_scalar(to_output_with = Scalar::to_output)] +#[graphql_scalar] +#[graphql(to_output_with = Scalar::to_output)] type CustomScalar = Scalar; impl Scalar { diff --git a/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr index 4ef029a4c..445ebfca2 100644 --- a/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr +++ b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr @@ -1,5 +1,11 @@ error: GraphQL scalar 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 - --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1 + --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:7:1 | -6 | type CustomScalar = Scalar; +7 | type CustomScalar = Scalar; | ^^^^ + +error: cannot find attribute `graphql` in this scope + --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:3 + | +6 | #[graphql(to_output_with = Scalar::to_output)] + | ^^^^^^^ diff --git a/tests/codegen/fail/scalar_value/missing_attributes.rs b/tests/codegen/fail/scalar_value/missing_attributes.rs index fb8f9c9bf..fb7fe2c67 100644 --- a/tests/codegen/fail/scalar_value/missing_attributes.rs +++ b/tests/codegen/fail/scalar_value/missing_attributes.rs @@ -1,12 +1,15 @@ +use derive_more::{Display, From, TryInto}; use juniper::ScalarValue; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, ScalarValue)] +#[derive(Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] pub enum DefaultScalarValue { Int(i32), + #[value(to_float)] Float(f64), - #[value(as_str, as_string, into_string)] + #[value(as_str, to_string)] String(String), - #[value(as_bool)] + #[value(to_bool)] Boolean(bool), } diff --git a/tests/codegen/fail/scalar_value/missing_attributes.stderr b/tests/codegen/fail/scalar_value/missing_attributes.stderr index b269e5a0d..bcd187e16 100644 --- a/tests/codegen/fail/scalar_value/missing_attributes.stderr +++ b/tests/codegen/fail/scalar_value/missing_attributes.stderr @@ -1,5 +1,11 @@ -error: GraphQL built-in scalars missing `#[value(as_int, as_float)]` attributes. In case you are sure that it's ok, use `#[value(allow_missing_attributes)]` to suppress this error. - --> fail/scalar_value/missing_attributes.rs:4:1 +error[E0277]: the trait bound `for<'a> DefaultScalarValue: TryScalarValueTo<'a, i32>` is not satisfied + --> fail/scalar_value/missing_attributes.rs:6:10 | -4 | pub enum DefaultScalarValue { - | ^^^ +6 | pub enum DefaultScalarValue { + | ^^^^^^^^^^^^^^^^^^ the trait `for<'a> TryScalarValueTo<'a, i32>` is not implemented for `DefaultScalarValue` + | + = help: the following other types implement trait `TryScalarValueTo<'me, T>`: + `DefaultScalarValue` implements `TryScalarValueTo<'_, &str>` + `DefaultScalarValue` implements `TryScalarValueTo<'_, bool>` + `DefaultScalarValue` implements `TryScalarValueTo<'_, f64>` + `DefaultScalarValue` implements `TryScalarValueTo<'_, std::string::String>` diff --git a/tests/codegen/src/lib.rs b/tests/codegen/src/lib.rs index 219ebd956..1b223d18d 100644 --- a/tests/codegen/src/lib.rs +++ b/tests/codegen/src/lib.rs @@ -5,8 +5,10 @@ #[cfg(test)] mod for_codegen_tests_only { + use derive_more as _; use futures as _; use juniper as _; + use serde as _; } #[rustversion::stable] From a00f807ccc53bbf50e2ba3027d127785f58325f7 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 18:35:56 +0200 Subject: [PATCH 17/22] Fix codegen tests and docs --- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/graphql_enum/mod.rs | 4 +- juniper_codegen/src/lib.rs | 153 +++++++++++++----------- 3 files changed, 87 insertions(+), 72 deletions(-) diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 06a346a54..d1d241919 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -29,7 +29,7 @@ syn = { version = "2.0", features = ["extra-traits", "full", "visit", "visit-mut url = "2.0" [dev-dependencies] -derive_more = { version = "2.0", features = ["from"] } +derive_more = { version = "2.0", features = ["from", "try_into"] } futures = "0.3.22" juniper = { path = "../juniper" } serde = "1.0.122" diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs index 12742c498..b8abf3a44 100644 --- a/juniper_codegen/src/graphql_enum/mod.rs +++ b/juniper_codegen/src/graphql_enum/mod.rs @@ -594,7 +594,9 @@ impl Definition { fn from_input_value( v: &::juniper::InputValue<#scalar>, ) -> ::core::result::Result { - match v.as_enum_value().or_else(|| v.as_scalar()?.try_as_str()) { + match v.as_enum_value() + .or_else(|| ::juniper::ScalarValue::try_as_str(v.as_scalar()?)) + { #( #variants )* _ => ::core::result::Result::Err( ::std::format!("Unknown enum value: {}", v), diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 6cd9fb8f6..e3eb44a96 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -365,15 +365,13 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { }) } -/// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][0] -/// implementation. +/// `#[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: +/// 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 +/// could be achieved by annotating the definition with the `#[graphql(transparent)]` attribute: /// ```rust /// # use juniper::{GraphQLObject, GraphQLScalar}; /// # @@ -415,8 +413,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// struct UserId(String); /// ``` /// -/// All the methods inherited from `Newtype`'s field may also be overridden -/// with the attributes described below. +/// All the methods inherited from `Newtype`'s field may also be overridden with the attributes +/// described below. /// /// # Custom resolving /// @@ -440,7 +438,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// Customization of a [GraphQL scalar][0] type parsing is possible via /// `#[graphql(from_input_with = )]` attribute: /// ```rust -/// # use juniper::{DefaultScalarValue, GraphQLScalar, ScalarValue}; +/// # use juniper::{GraphQLScalar, ScalarValue}; /// # /// #[derive(GraphQLScalar)] /// #[graphql(from_input_with = Self::from_input, transparent)] @@ -450,34 +448,57 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// /// Checks whether the [`ScalarValue`] is a [`String`] beginning with /// /// `id: ` and strips it. /// fn from_input( -/// input: &impl ScalarValue, -/// ) -> Result { -/// // ^^^^^^ must implement `IntoFieldError` -/// input.try_as_str() -/// .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}", -/// ) -/// }) +/// input: &str, +/// // ^^^^ any concrete type having `TryScalarValueTo` implementation could be used +/// ) -> Result> { +/// // ^^^^^^^^ must implement `IntoFieldError` +/// input +/// .strip_prefix("id: ") +/// .ok_or_else(|| { +/// format!("Expected `UserId` to begin with `id: `, found: {input}").into() /// }) /// .map(|id| Self(id.into())) /// } /// } /// ``` /// +/// The provided function is polymorphic by input and output types: +/// ```rust +/// # use juniper::{GraphQLScalar, Scalar, ScalarValue}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql(from_input_with = Self::from_input, transparent)] +/// struct UserId(String); +/// +/// impl UserId { +/// fn from_input( +/// input: &Scalar, +/// // ^^^^^^ for generic argument using `Scalar` transparent wrapper is required, +/// // otherwise Rust won't be able to infer the required type +/// ) -> Self { +/// // ^^^^ if the result is infallible, it's OK to not use `Result` +/// Self( +/// input +/// .try_to_int().map(|i| i.to_string()) +/// .or_else(|| input.try_to_bool().map(|f| f.to_string())) +/// .or_else(|| input.try_to_float().map(|b| b.to_string())) +/// .or_else(|| input.try_to_string()) +/// .unwrap_or_else(|| { +/// unreachable!("`ScalarValue` is at least one of primitive GraphQL types") +/// }), +/// ) +/// } +/// } +/// ``` +/// /// # 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: +/// 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, ParseScalarResult, ParseScalarValue, ScalarValue, -/// # ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, +/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -501,11 +522,11 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// fn from_input(v: &impl ScalarValue) -> Result { +/// fn from_input(v: &Scalar) -> Result> { /// v.try_to_string() /// .map(StringOrInt::String) /// .or_else(|| v.try_to_int().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) /// } /// /// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -513,18 +534,17 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// .or_else(|_| >::from_str(value)) /// } /// ``` -/// > __NOTE:__ Once we provide all 3 custom functions, there is no sense in -/// > following the [newtype pattern][1] anymore. +/// > __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: +/// 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, ParseScalarResult, ParseScalarValue, ScalarValue, -/// # ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, +/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -544,11 +564,11 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// pub(super) fn from_input(v: &impl ScalarValue) -> Result { +/// pub(super) fn from_input(v: &Scalar) -> Result> { /// v.try_to_string() /// .map(StringOrInt::String) /// .or_else(|| v.try_to_int().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) /// } /// /// pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -563,8 +583,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// A regular `impl` block is also suitable for that: /// ```rust /// # use juniper::{ -/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, ScalarValue, -/// # ScalarToken, Value, +/// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, +/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -582,11 +602,11 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// fn from_input(v: &impl ScalarValue) -> Result { +/// fn from_input(v: &Scalar) -> Result> { /// v.try_to_string() /// .map(Self::String) /// .or_else(|| v.try_to_int().map(Self::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) /// } /// /// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult @@ -603,9 +623,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// /// At the same time, any custom function still may be specified separately: /// ```rust -/// # use juniper::{ -/// # GraphQLScalar, ParseScalarResult, ScalarValue, ScalarToken, Value, -/// # }; +/// # use juniper::{GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, Value}; /// # /// #[derive(GraphQLScalar)] /// #[graphql( @@ -630,11 +648,11 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// } /// -/// pub(super) fn from_input(v: &impl ScalarValue) -> Result { +/// pub(super) fn from_input(v: &Scalar) -> Result> { /// v.try_to_string() /// .map(StringOrInt::String) /// .or_else(|| v.try_to_int().map(StringOrInt::Int)) -/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}")) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) /// } /// /// // No need in `parse_token()` function. @@ -645,18 +663,17 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// /// # 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. +/// By default, this macro generates code, which is generic over a [`ScalarValue`] type. Concrete +/// [`ScalarValue`] type may be specified via the `#[graphql(scalar = )]` attribute. /// -/// It also may be used to provide additional bounds to the [`ScalarValue`] -/// generic, like the following: `#[graphql(scalar = S: Trait)]`. +/// 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))]`. +/// [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 @@ -670,9 +687,8 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { }) } -/// `#[graphql_scalar]` macro.is interchangeable with -/// `#[derive(`[`GraphQLScalar`]`)]` macro, and is used for deriving a -/// [GraphQL scalar][0] implementation. +/// `#[graphql_scalar]` macro.is interchangeable with the `#[derive(`[`GraphQLScalar`]`)]` macro, +/// and is used for deriving a [GraphQL scalar][0] implementation. /// /// ```rust /// # use juniper::graphql_scalar; @@ -696,11 +712,10 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// /// # Foreign types /// -/// Additionally, `#[graphql_scalar]` can be used directly on foreign types via -/// type alias, without using the [newtype pattern][1]. +/// 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. +/// > __NOTE:__ To satisfy [orphan rules] you should provide local [`ScalarValue`] implementation. /// /// ```rust /// # mod date { @@ -740,10 +755,8 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// Value::scalar(v.to_string()) /// } /// -/// pub(super) fn from_input(v: &CustomScalarValue) -> Result { -/// v.try_as_str() -/// .ok_or_else(|| format!("Expected `String`, found: {v}")) -/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}"))) +/// pub(super) fn from_input(s: &str) -> Result> { +/// s.parse().map_err(|e| format!("Failed to parse `Date`: {e}").into()) /// } /// } /// # @@ -777,10 +790,10 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// ```rust /// # use std::{any::Any, fmt}; /// # -/// # use serde::{de, Deserialize, Deserializer, Serialize}; +/// use derive_more::with_trait::{Display, From, TryInto}; /// # use juniper::ScalarValue; -/// # -/// #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] +/// # use serde::{de, Deserialize, Deserializer, Serialize}; +/// #[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)] /// #[serde(untagged)] /// #[value(from_displayable_with = from_custom_str)] /// enum MyScalarValue { @@ -801,7 +814,7 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// /// // Custom implementation of `ScalarValue::from_displayable()` method for /// // possible efficient conversions into `MyScalarValue` from custom string types. -/// fn from_custom_str(s: &Str) -> MyScalarValue { +/// fn from_custom_str(s: &Str) -> MyScalarValue { /// use juniper::AnyExt as _; // allows downcasting directly on types without `dyn` /// /// // Imagine this is some custom optimized string type. From b6db4305eb4000c5f7dc9f9f5556e215f953d10d Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 18:59:58 +0200 Subject: [PATCH 18/22] Fix Book --- book/src/types/scalars.md | 131 +++++++++++++++++++++---------------- juniper_codegen/src/lib.rs | 3 +- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/book/src/types/scalars.md b/book/src/types/scalars.md index 2d0ba81e5..9956bb77a 100644 --- a/book/src/types/scalars.md +++ b/book/src/types/scalars.md @@ -105,31 +105,57 @@ fn to_output(v: &Incremented) -> Value { Customization of a [custom GraphQL scalar][2] value parsing is possible via `#[graphql(from_input_with = )]` attribute: ```rust # extern crate juniper; -# use juniper::{GraphQLScalar, InputValue, ScalarValue}; +# use juniper::{GraphQLScalar, ScalarValue}; # #[derive(GraphQLScalar)] #[graphql(from_input_with = Self::from_input, transparent)] struct UserId(String); impl UserId { - /// Checks whether the [`InputValue`] is a [`String`] beginning with `id: ` - /// and strips it. - fn from_input(input: &InputValue) -> Result - where - S: ScalarValue - { - 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}", - ) - }) + /// Checks whether the [`InputValue`] is a [`String`] beginning with `id: ` and strips it. + fn from_input( + input: &str, + // ^^^^ any concrete type having `TryScalarValueTo` implementation could be used + ) -> Result> { + // ^^^^^^^^ must implement `IntoFieldError` + input + .strip_prefix("id: ") + .ok_or_else(|| { + format!("Expected `UserId` to begin with `id: `, found: {input}").into() }) - .map(|id| Self(id.to_owned())) + .map(|id| Self(id.into())) + } +} +# +# fn main() {} +``` + +The provided function is polymorphic by input and output types: +```rust +# extern crate juniper; +# use juniper::{GraphQLScalar, Scalar, ScalarValue}; +# +#[derive(GraphQLScalar)] +#[graphql(from_input_with = Self::from_input, transparent)] +struct UserId(String); + +impl UserId { + fn from_input( + input: &Scalar, + // ^^^^^^ for generic argument using `Scalar` transparent wrapper is required, + // otherwise Rust won't be able to infer the required type + ) -> Self { + // ^^^^ if the result is infallible, it's OK to not use `Result` + Self( + input + .try_to_int().map(|i| i.to_string()) + .or_else(|| input.try_to_bool().map(|f| f.to_string())) + .or_else(|| input.try_to_float().map(|b| b.to_string())) + .or_else(|| input.try_to_string()) + .unwrap_or_else(|| { + unreachable!("`ScalarValue` is at least one of primitive GraphQL types") + }), + ) } } # @@ -143,8 +169,7 @@ Customization of which tokens a [custom GraphQL scalar][0] type should be parsed ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -# ScalarValue, ScalarToken, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, # }; # #[derive(GraphQLScalar)] @@ -168,11 +193,11 @@ fn to_output(v: &StringOrInt) -> Value { } } -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 from_input(v: &Scalar) -> Result> { + v.try_to_string() + .map(StringOrInt::String) + .or_else(|| v.try_to_int().map(StringOrInt::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -191,8 +216,7 @@ Instead of providing all custom functions separately, it's possible to provide a ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -# ScalarValue, ScalarToken, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, # }; # #[derive(GraphQLScalar)] @@ -212,11 +236,11 @@ mod string_or_int { } } - 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 from_input(v: &Scalar) -> Result> { + v.try_to_string() + .map(StringOrInt::String) + .or_else(|| v.try_to_int().map(StringOrInt::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult { @@ -232,8 +256,7 @@ A regular `impl` block is also suitable for that: ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, -# ScalarValue, ScalarToken, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, # }; # #[derive(GraphQLScalar)] @@ -251,14 +274,11 @@ impl StringOrInt { } } - 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 from_input(v: &Scalar) -> Result> { + v.try_to_string() + .map(Self::String) + .or_else(|| v.try_to_int().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult @@ -277,8 +297,7 @@ At the same time, any custom function still may be specified separately, if requ ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, -# ScalarToken, Value +# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, Value, # }; # #[derive(GraphQLScalar)] @@ -304,14 +323,11 @@ mod string_or_int { } } - 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}")) + pub(super) fn from_input(v: &Scalar) -> Result> { + v.try_to_string() + .map(StringOrInt::String) + .or_else(|| v.try_to_int().map(StringOrInt::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into()) } // No need in `parse_token()` function. @@ -351,9 +367,10 @@ For implementing [custom scalars][2] on foreign types there is [`#[graphql_scala # } # # use juniper::DefaultScalarValue as CustomScalarValue; -use juniper::{InputValue, ScalarValue, Value, graphql_scalar}; +use juniper::{ScalarValue, Value, graphql_scalar}; -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_scalar, parse_token(String), scalar = CustomScalarValue, @@ -369,10 +386,8 @@ mod date_scalar { 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}"))) + pub(super) fn from_input(s: &str) -> Result> { + s.parse().map_err(|e| format!("Failed to parse `Date`: {e}").into()) } } # diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index e3eb44a96..ab977ce91 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -445,8 +445,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// struct UserId(String); /// /// impl UserId { -/// /// Checks whether the [`ScalarValue`] is a [`String`] beginning with -/// /// `id: ` and strips it. +/// /// Checks whether the [`ScalarValue`] is a [`String`] beginning with `id: ` and strips it. /// fn from_input( /// input: &str, /// // ^^^^ any concrete type having `TryScalarValueTo` implementation could be used From c4d8766c688b6c0ba27bd78be65d62870e31bbcf Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 19:28:46 +0200 Subject: [PATCH 19/22] Fill up CHANGELOG for `juniper_codegen` crate --- juniper_codegen/CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md index d4d51a8c1..1e119e572 100644 --- a/juniper_codegen/CHANGELOG.md +++ b/juniper_codegen/CHANGELOG.md @@ -10,14 +10,28 @@ All user visible changes to `juniper_codegen` crate will be documented in this f ### BC Breaks +- `#[derive(ScalarValue)]` macro: ([#1327]) + - Renamed `#[value(as_bool)]` attribute as `#[value(to_bool)]`. + - Renamed `#[value(as_float)]` attribute as `#[value(to_float)]`. + - Renamed `#[value(as_int)]` attribute as `#[value(to_int)]`. + - Renamed `#[value(as_string)]` attribute as `#[value(to_string)]`. + - Removed `#[value(into_string)]` attribute. + - Removed `#[value(allow_missing_attributes)]` attribute. + - `From` and `Display` implementations are not derived anymore. +- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) + - Made provided `from_input()` function to accept `ScalarValue` directly instead of `InputValue`. - Bumped up [MSRV] to 1.85. ([#1272], [1b1fc618]) ### Added -- Support of top-level `#[value(from_displayable_with = ...)]` attribute in `derive(ScalarValue)`. ([#1324]) +- Support of top-level `#[value(from_displayable_with = ...)]` attribute in `#[derive(ScalarValue)]` macro. ([#1324]) +- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) + - Support for specifying concrete types as input argument in provided `from_input()` function. + - Support for non-`Result` return type in provided `from_input()` function. [#1272]: /../../pull/1272 [#1324]: /../../pull/1324 +[#1327]: /../../pull/1327 [1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295 From bb2cc0fcafa07ab11f06af76c55a967afca770da Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 19:57:22 +0200 Subject: [PATCH 20/22] Fill up CHANGELOG for `juniper` crate --- juniper/CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++- juniper_codegen/CHANGELOG.md | 5 +++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 9efc8ffa2..4479b1aa1 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -67,6 +67,31 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([20609366]) - Replaced `Value`'s `From` implementations with `IntoValue` ones. ([#1324]) - Replaced `InputValue`'s `From` implementations with `IntoInputValue` ones. ([#1324]) +- `Value` enum: ([#1327]) + - Removed `as_float_value()`, `as_string_value()` and `as_scalar_value()` methods (use `as_scalar()` method and then `ScalarValue` methods instead). +- `InputValue` enum: ([#1327]) + - Removed `as_float_value()`, `as_int_value()`, `as_string_value()` and `as_scalar_value()` methods (use `as_scalar()` method and then `ScalarValue` methods instead). +- `ScalarValue` trait: ([#1327]) + - Switched from `From` conversions to `TryScalarValueTo` conversions. + - Made to require `TryScalarValueTo` conversions for `bool`, `f64`, `i32`, `String` and `&str` types (could be derived with `#[value()]` attributes of `#[derive(ScalarValue)]` macro). + - Made to require `TryInto` conversion (could be derived with `derive_more::TryInto`). + - Made `is_type()` method required and to accept `Any` type. + - Renamed `as_bool()` method to as `try_to_bool()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_float()` method to as `try_to_float()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_int()` method to as `try_to_int()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_string()` method to as `try_to_string()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_str()` method to as `try_as_str()` and made it defined by default as `TryScalarValueTo<&str>` alias. + - Renamed `into_string()` method to as `try_into_string()` and made it defined by default as `TryInto` alias. +- `#[derive(ScalarValue)]` macro: ([#1327]) + - Renamed `#[value(as_bool)]` attribute as `#[value(to_bool)]`. + - Renamed `#[value(as_float)]` attribute as `#[value(to_float)]`. + - Renamed `#[value(as_int)]` attribute as `#[value(to_int)]`. + - Renamed `#[value(as_string)]` attribute as `#[value(to_string)]`. + - Removed `#[value(into_string)]` attribute. + - Removed `#[value(allow_missing_attributes)]` attribute (now attributes can always be omitted). + - `From` and `Display` implementations are not derived anymore (recommended way is to use [`derive_more` crate] for this). +- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) + - Made provided `from_input()` function to accept `ScalarValue` (or anything `TryScalarValueTo`-convertible) directly instead of `InputValue`. ### Added @@ -82,8 +107,15 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - `http::GraphQLResponse::into_result()` method. ([#1293]) - `String` scalar implementation for `arcstr::ArcStr`. ([#1247]) - `String` scalar implementation for `compact_str::CompactString`. ([20609366]) -- `ScalarValue::from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819]) - `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324]) +- `ScalarValue` trait: ([#1327]) + - `from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819]) + - `try_to::()` method defined by default as `TryScalarValueTo` alias. +- `TryScalarValueTo` conversion trait aiding `ScalarValue` trait. ([#1327]) +- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) + - Support for specifying concrete types as input argument in provided `from_input()` function. + - Support for non-`Result` return type in provided `from_input()` function. + - `Scalar` transparent wrapper for aiding type inference in `from_input()` function when input argument is generic `ScalarValue`. ### Changed @@ -110,6 +142,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1318]: /../../pull/1318 [#1324]: /../../pull/1324 [#1326]: /../../pull/1326 +[#1327]: /../../pull/1327 [1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295 [20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3 @@ -313,6 +346,7 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md). [`bson` crate]: https://docs.rs/bson [`chrono` crate]: https://docs.rs/chrono [`chrono-tz` crate]: https://docs.rs/chrono-tz +[`derive_more` crate]: https://docs.rs/derive_more [`jiff` crate]: https://docs.rs/jiff [`time` crate]: https://docs.rs/time [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md index 1e119e572..ef1814ee2 100644 --- a/juniper_codegen/CHANGELOG.md +++ b/juniper_codegen/CHANGELOG.md @@ -10,6 +10,7 @@ All user visible changes to `juniper_codegen` crate will be documented in this f ### BC Breaks +- Bumped up [MSRV] to 1.85. ([#1272], [1b1fc618]) - `#[derive(ScalarValue)]` macro: ([#1327]) - Renamed `#[value(as_bool)]` attribute as `#[value(to_bool)]`. - Renamed `#[value(as_float)]` attribute as `#[value(to_float)]`. @@ -20,11 +21,11 @@ All user visible changes to `juniper_codegen` crate will be documented in this f - `From` and `Display` implementations are not derived anymore. - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) - Made provided `from_input()` function to accept `ScalarValue` directly instead of `InputValue`. -- Bumped up [MSRV] to 1.85. ([#1272], [1b1fc618]) ### Added -- Support of top-level `#[value(from_displayable_with = ...)]` attribute in `#[derive(ScalarValue)]` macro. ([#1324]) +- `#[derive(ScalarValue)]` macro: ([#1324]) + - Support of top-level `#[value(from_displayable_with = ...)]` attribute. - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) - Support for specifying concrete types as input argument in provided `from_input()` function. - Support for non-`Result` return type in provided `from_input()` function. From 9ee5ac74981df28eff945292b5b192699e998f7e Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 20:33:31 +0200 Subject: [PATCH 21/22] Fix typos --- juniper/CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 4479b1aa1..96a2e1665 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -76,12 +76,12 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Made to require `TryScalarValueTo` conversions for `bool`, `f64`, `i32`, `String` and `&str` types (could be derived with `#[value()]` attributes of `#[derive(ScalarValue)]` macro). - Made to require `TryInto` conversion (could be derived with `derive_more::TryInto`). - Made `is_type()` method required and to accept `Any` type. - - Renamed `as_bool()` method to as `try_to_bool()` and made it defined by default as `TryScalarValueTo` alias. - - Renamed `as_float()` method to as `try_to_float()` and made it defined by default as `TryScalarValueTo` alias. - - Renamed `as_int()` method to as `try_to_int()` and made it defined by default as `TryScalarValueTo` alias. - - Renamed `as_string()` method to as `try_to_string()` and made it defined by default as `TryScalarValueTo` alias. - - Renamed `as_str()` method to as `try_as_str()` and made it defined by default as `TryScalarValueTo<&str>` alias. - - Renamed `into_string()` method to as `try_into_string()` and made it defined by default as `TryInto` alias. + - Renamed `as_bool()` method as `try_to_bool()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_float()` method as `try_to_float()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_int()` method as `try_to_int()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_string()` method as `try_to_string()` and made it defined by default as `TryScalarValueTo` alias. + - Renamed `as_str()` method as `try_as_str()` and made it defined by default as `TryScalarValueTo<&str>` alias. + - Renamed `into_string()` method as `try_into_string()` and made it defined by default as `TryInto` alias. - `#[derive(ScalarValue)]` macro: ([#1327]) - Renamed `#[value(as_bool)]` attribute as `#[value(to_bool)]`. - Renamed `#[value(as_float)]` attribute as `#[value(to_float)]`. From 754fe65e1c667801014ff32b721309328778677b Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 13 Jun 2025 20:40:24 +0200 Subject: [PATCH 22/22] Fix typos --- juniper/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 96a2e1665..f48d0415a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -108,9 +108,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - `String` scalar implementation for `arcstr::ArcStr`. ([#1247]) - `String` scalar implementation for `compact_str::CompactString`. ([20609366]) - `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324]) -- `ScalarValue` trait: ([#1327]) +- `ScalarValue` trait: - `from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819]) - - `try_to::()` method defined by default as `TryScalarValueTo` alias. + - `try_to::()` method defined by default as `TryScalarValueTo` alias. ([#1327]) - `TryScalarValueTo` conversion trait aiding `ScalarValue` trait. ([#1327]) - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327]) - Support for specifying concrete types as input argument in provided `from_input()` function.