From dee7a5bd77bf5e6f05d569fe86a228b7d58939c2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 18 Jun 2025 17:16:15 +0200 Subject: [PATCH 01/14] Bootstrap trait --- juniper/src/lib.rs | 2 +- juniper/src/value/mod.rs | 2 +- juniper/src/value/scalar.rs | 17 +++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 7741d297f..09efe903e 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult, - ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, + ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, IntoScalarValue, }, }; diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e6deed359..4b4bb9c13 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -10,7 +10,7 @@ pub use self::{ object::Object, scalar::{ AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar, - ScalarValue, TryToPrimitive, WrongInputScalarTypeError, + ScalarValue, TryToPrimitive, WrongInputScalarTypeError, IntoScalarValue, }, }; use crate::{ diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index e0cd173aa..5eca5d86a 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, - parser::{ParseError, ScalarToken}, -}; +use crate::{FieldError, IntoFieldError, parser::{ParseError, ScalarToken}, Value}; #[cfg(doc)] use crate::{GraphQLScalar, GraphQLValue, Value}; @@ -570,6 +567,18 @@ impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> } } +pub trait IntoScalarValue: Sized { + /// Converts this value into a [`ScalarValue`]. + #[must_use] + fn into_scalar_value(self) -> S; +} + +impl IntoScalarValue for S { + fn into_scalar_value(self) -> Self { + self + } +} + /// Transparent wrapper over a value, indicating it being a [`ScalarValue`]. /// /// Used in [`GraphQLScalar`] definitions to distinguish a concrete type for a generic From fea4146a5347db961f70b054481d4bc1790dba40 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 18 Jun 2025 17:40:41 +0200 Subject: [PATCH 02/14] Remake to accept ref --- juniper/src/lib.rs | 5 +-- juniper/src/types/pointers.rs | 29 +++++++++++++++- juniper/src/types/scalars.rs | 62 +++++++++++++++++++++++++++++------ juniper/src/value/mod.rs | 4 +-- juniper/src/value/scalar.rs | 15 ++++----- 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 09efe903e..355b37726 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -103,8 +103,9 @@ pub use crate::{ }, validation::RuleError, value::{ - AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult, - ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, IntoScalarValue, + AnyExt, DefaultScalarValue, FromScalarValue, ToScalarValue, IntoValue, Object, + ParseScalarResult, ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, + WrongInputScalarTypeError, }, }; diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 1bf66f5a0..b80ca3283 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -11,7 +11,7 @@ use crate::{ async_await::GraphQLValueAsync, base::{Arguments, GraphQLType, GraphQLValue}, }, - value::{FromScalarValue, ScalarValue}, + value::{FromScalarValue, ToScalarValue, ScalarValue}, }; impl GraphQLType for Box @@ -99,6 +99,15 @@ where } } +impl ToScalarValue for Box +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Box where S: ScalarValue, @@ -204,6 +213,15 @@ where } } +impl ToScalarValue for &T +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl ToInputValue for &T where S: fmt::Debug, @@ -299,6 +317,15 @@ where } } +impl ToScalarValue for Arc +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Arc where S: ScalarValue, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 272fca57c..650f7a6e5 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -17,7 +17,7 @@ use crate::{ subscriptions::GraphQLSubscriptionValue, }, value::{ - FromScalarValue, ParseScalarResult, ScalarValue, TryToPrimitive, Value, + FromScalarValue, ParseScalarResult, ScalarValue, ToScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, }, }; @@ -76,6 +76,15 @@ mod impl_string_scalar { } } + impl ToScalarValue for String + where + Self: Into, + { + fn to_scalar_value(&self) -> S { + self.clone().into() + } + } + pub(super) fn to_output(v: &str) -> Value { Value::scalar(v.to_owned()) } @@ -291,15 +300,6 @@ where } } -impl ToInputValue for &str -where - S: ScalarValue, -{ - fn to_input_value(&self) -> InputValue { - InputValue::scalar(String::from(*self)) - } -} - impl<'s, S> FromScalarValue<'s, S> for &'s str where S: TryToPrimitive<'s, Self, Error: IntoFieldError> + 's, @@ -311,6 +311,21 @@ where } } +impl ToScalarValue for str { + fn to_scalar_value(&self) -> S { + S::from_displayable(self) + } +} + +impl ToInputValue for &str +where + str: ToScalarValue, +{ + fn to_input_value(&self) -> InputValue { + InputValue::scalar::(self.to_scalar_value()) + } +} + #[graphql_scalar] #[graphql(with = impl_boolean_scalar, from_input_with = __builtin)] type Boolean = bool; @@ -329,6 +344,15 @@ mod impl_boolean_scalar { } } + impl ToScalarValue for Boolean + where + Self: Into, + { + fn to_scalar_value(&self) -> S { + (*self).into() + } + } + pub(super) fn to_output(v: &Boolean) -> Value { Value::scalar(*v) } @@ -357,6 +381,15 @@ mod impl_int_scalar { } } + impl ToScalarValue for Int + where + Self: Into, + { + fn to_scalar_value(&self) -> S { + (*self).into() + } + } + pub(super) fn to_output(v: &Int) -> Value { Value::scalar(*v) } @@ -390,6 +423,15 @@ mod impl_float_scalar { } } + impl ToScalarValue for Float + where + Self: Into, + { + fn to_scalar_value(&self) -> S { + (*self).into() + } + } + pub(super) fn to_output(v: &Float) -> Value { Value::scalar(*v) } diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 4b4bb9c13..476290eea 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -9,8 +9,8 @@ use compact_str::CompactString; pub use self::{ object::Object, scalar::{ - AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar, - ScalarValue, TryToPrimitive, WrongInputScalarTypeError, IntoScalarValue, + AnyExt, DefaultScalarValue, FromScalarValue, ToScalarValue, ParseScalarResult, + ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, WrongInputScalarTypeError, }, }; use crate::{ diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 5eca5d86a..5a3a437ae 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, parser::{ParseError, ScalarToken}, Value}; +use crate::{ + FieldError, IntoFieldError, + parser::{ParseError, ScalarToken}, +}; #[cfg(doc)] use crate::{GraphQLScalar, GraphQLValue, Value}; @@ -567,16 +570,10 @@ impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> } } -pub trait IntoScalarValue: Sized { +pub trait ToScalarValue { /// Converts this value into a [`ScalarValue`]. #[must_use] - fn into_scalar_value(self) -> S; -} - -impl IntoScalarValue for S { - fn into_scalar_value(self) -> Self { - self - } + fn to_scalar_value(&self) -> S; } /// Transparent wrapper over a value, indicating it being a [`ScalarValue`]. From 63e751e136c4cd8875d223093caeb7aff4247a15 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 18 Jun 2025 17:49:21 +0200 Subject: [PATCH 03/14] Strip `Sized` from `ToInputValue` --- juniper/src/ast.rs | 4 ++-- juniper/src/lib.rs | 4 ++-- juniper/src/types/containers.rs | 10 +++++----- juniper/src/types/nullable.rs | 5 ++--- juniper/src/types/pointers.rs | 13 +++++-------- juniper/src/types/scalars.rs | 2 +- juniper/src/value/mod.rs | 4 ++-- 7 files changed, 19 insertions(+), 23 deletions(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index ebf329cce..cb68ee4db 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -231,8 +231,8 @@ pub trait FromInputValue: Sized { } } -/// Losslessly clones a Rust data type into an InputValue. -pub trait ToInputValue: Sized { +/// Losslessly clones a Rust data type into an [`InputValue`]. +pub trait ToInputValue { /// Performs the conversion. fn to_input_value(&self) -> InputValue; } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 355b37726..373ee877b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -103,8 +103,8 @@ pub use crate::{ }, validation::RuleError, value::{ - AnyExt, DefaultScalarValue, FromScalarValue, ToScalarValue, IntoValue, Object, - ParseScalarResult, ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, + AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult, + ParseScalarValue, Scalar, ScalarValue, ToScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, }, }; diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 4ea31b990..58047dd8f 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -90,7 +90,10 @@ impl> FromInputValue for Option { } } -impl> ToInputValue for Option { +impl ToInputValue for Option +where + T: ToInputValue, +{ fn to_input_value(&self) -> InputValue { match self { Some(v) => v.to_input_value(), @@ -176,7 +179,6 @@ impl> FromInputValue for Vec { impl ToInputValue for Vec where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -270,10 +272,9 @@ where } } -impl ToInputValue for &[T] +impl ToInputValue for [T] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -462,7 +463,6 @@ where impl ToInputValue for [T; N] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 3c4b47895..79d399f68 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -302,11 +302,10 @@ impl> FromInputValue for Nullable { impl ToInputValue for Nullable where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { - match *self { - Self::Some(ref v) => v.to_input_value(), + match self { + Self::Some(v) => v.to_input_value(), _ => InputValue::null(), } } diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index b80ca3283..a3d496f7a 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -1,4 +1,4 @@ -use std::{fmt, sync::Arc}; +use std::sync::Arc; use arcstr::ArcStr; @@ -11,7 +11,7 @@ use crate::{ async_await::GraphQLValueAsync, base::{Arguments, GraphQLType, GraphQLValue}, }, - value::{FromScalarValue, ToScalarValue, ScalarValue}, + value::{FromScalarValue, ScalarValue, ToScalarValue}, }; impl GraphQLType for Box @@ -122,8 +122,7 @@ where impl ToInputValue for Box where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -224,8 +223,7 @@ where impl ToInputValue for &T where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -340,8 +338,7 @@ where impl ToInputValue for Arc where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 650f7a6e5..3812af629 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -317,7 +317,7 @@ impl ToScalarValue for str { } } -impl ToInputValue for &str +impl ToInputValue for str where str: ToScalarValue, { diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 476290eea..ec2b08f80 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -9,8 +9,8 @@ use compact_str::CompactString; pub use self::{ object::Object, scalar::{ - AnyExt, DefaultScalarValue, FromScalarValue, ToScalarValue, ParseScalarResult, - ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, WrongInputScalarTypeError, + AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar, + ScalarValue, ToScalarValue, TryToPrimitive, WrongInputScalarTypeError, }, }; use crate::{ From db9a189b85e6648b61e201f68265fcfbe1506e52 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 18 Jun 2025 22:53:34 +0200 Subject: [PATCH 04/14] Change macro expansion and apply ergonomics --- juniper/src/executor_tests/variables.rs | 6 +- juniper/src/integrations/bigdecimal.rs | 25 ++- juniper/src/integrations/bson.rs | 24 +-- juniper/src/integrations/chrono.rs | 70 ++++----- juniper/src/integrations/chrono_tz.rs | 11 +- juniper/src/integrations/jiff.rs | 106 ++++++------- juniper/src/integrations/rust_decimal.rs | 22 ++- juniper/src/integrations/time.rs | 72 ++++----- juniper/src/integrations/url.rs | 12 +- juniper/src/integrations/uuid.rs | 11 +- juniper/src/macros/helper/mod.rs | 33 +++- juniper/src/types/scalars.rs | 76 +++------ juniper_codegen/src/graphql_scalar/mod.rs | 179 ++++++++++++++-------- 13 files changed, 332 insertions(+), 315 deletions(-) diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 387436ad0..060487793 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,5 +1,5 @@ use crate::{ - GraphQLInputObject, GraphQLScalar, ScalarValue, Value, + GraphQLInputObject, GraphQLScalar, executor::Variables, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, @@ -14,8 +14,8 @@ use crate::{ struct TestComplexScalar; impl TestComplexScalar { - fn to_output(&self) -> Value { - graphql_value!("SerializedValue") + fn to_output(&self) -> &'static str { + "SerializedValue" } fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 1defe431b..b4b0bf5f2 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -8,9 +8,7 @@ //! //! [`BigDecimal`]: bigdecimal::BigDecimal -use std::str::FromStr as _; - -use crate::{Scalar, ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -29,7 +27,8 @@ mod for_minimal_versions_check_only { /// See also [`bigdecimal`] crate for details. /// /// [`bigdecimal`]: https://docs.rs/bigdecimal -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = bigdecimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/bigdecimal", @@ -37,10 +36,11 @@ mod for_minimal_versions_check_only { type BigDecimal = bigdecimal::BigDecimal; mod bigdecimal_scalar { - use super::*; + use super::BigDecimal; + use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &BigDecimal) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &BigDecimal) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(v: &Scalar) -> Result> { @@ -50,13 +50,14 @@ mod bigdecimal_scalar { // See akubera/bigdecimal-rs#103 for details: // https://github.com/akubera/bigdecimal-rs/issues/103 let mut buf = ryu::Buffer::new(); - BigDecimal::from_str(buf.format(f)) + buf.format(f) + .parse::() .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into()) } else { v.try_to::<&str>() .map_err(|e| e.to_string().into()) .and_then(|s| { - BigDecimal::from_str(s).map_err(|e| { + s.parse::().map_err(|e| { format!("Failed to parse `BigDecimal` from `String`: {e}").into() }) }) @@ -66,8 +67,6 @@ mod bigdecimal_scalar { #[cfg(test)] mod test { - use std::str::FromStr as _; - use crate::{FromInputValue as _, InputValue, ToInputValue as _, graphql_input_value}; use super::BigDecimal; @@ -91,7 +90,7 @@ mod test { ] { let input: InputValue = input; let parsed = BigDecimal::from_input_value(&input); - let expected = BigDecimal::from_str(expected).unwrap(); + let expected = expected.parse::().unwrap(); assert!( parsed.is_ok(), @@ -130,7 +129,7 @@ mod test { "123", "43.44", ] { - let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); + let actual: InputValue = raw.parse::().unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 509b3bc12..54b27b518 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -13,7 +13,7 @@ //! [s1]: https://graphql-scalars.dev/docs/scalars/object-id //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `bson` crate. mod for_minimal_versions_check_only { @@ -29,7 +29,8 @@ mod for_minimal_versions_check_only { /// [0]: https://www.mongodb.com/docs/manual/reference/bson-types#objectid /// [1]: https://graphql-scalars.dev/docs/scalars/object-id /// [2]: https://docs.rs/bson/*/bson/oid/struct.ObjectId.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "ObjectID", with = object_id, parse_token(String), @@ -38,10 +39,10 @@ mod for_minimal_versions_check_only { type ObjectId = bson::oid::ObjectId; mod object_id { - use super::*; + use super::ObjectId; - pub(super) fn to_output(v: &ObjectId) -> Value { - Value::scalar(v.to_hex()) + pub(super) fn to_output(v: &ObjectId) -> String { + v.to_hex() } pub(super) fn from_input(s: &str) -> Result> { @@ -62,7 +63,8 @@ mod object_id { /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/bson/*/bson/struct.DateTime.html /// [3]: https://www.mongodb.com/docs/manual/reference/bson-types#date -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -70,13 +72,11 @@ mod object_id { type DateTime = bson::DateTime; mod date_time { - use super::*; + use super::DateTime; - pub(super) fn to_output(v: &DateTime) -> Value { - Value::scalar( - (*v).try_to_rfc3339_string() - .unwrap_or_else(|e| panic!("failed to format `DateTime` as RFC 3339: {e}")), - ) + pub(super) fn to_output(v: &DateTime) -> String { + (*v).try_to_rfc3339_string() + .unwrap_or_else(|e| panic!("failed to format `DateTime` as RFC 3339: {e}")) } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 99e380c8b..ec437c677 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -23,7 +23,7 @@ use std::fmt; use chrono::{FixedOffset, TimeZone}; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -36,7 +36,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -44,18 +45,15 @@ use crate::{ScalarValue, Value, graphql_scalar}; pub type LocalDate = chrono::NaiveDate; mod local_date { - use super::*; + use super::LocalDate; /// Format of a [`LocalDate` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> Value - where - S: ScalarValue, - { - Value::scalar(v.format(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDate) -> String { + v.format(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -75,7 +73,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -85,7 +84,7 @@ pub type LocalTime = chrono::NaiveTime; mod local_time { use chrono::Timelike as _; - use super::*; + use super::LocalTime; /// Full format of a [`LocalTime` scalar][1]. /// @@ -102,18 +101,13 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> Value - where - S: ScalarValue, - { - Value::scalar( - if v.nanosecond() == 0 { - v.format(FORMAT_NO_MILLIS) - } else { - v.format(FORMAT) - } - .to_string(), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + if v.nanosecond() == 0 { + v.format(FORMAT_NO_MILLIS) + } else { + v.format(FORMAT) + } + .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -134,7 +128,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -142,18 +137,15 @@ mod local_time { pub type LocalDateTime = chrono::NaiveDateTime; mod local_date_time { - use super::*; + use super::LocalDateTime; /// Format of a [`LocalDateTime` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.format(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDateTime) -> String { + v.format(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -174,7 +166,8 @@ mod local_date_time { /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -186,20 +179,19 @@ mod local_date_time { pub type DateTime = chrono::DateTime; mod date_time { - use chrono::{SecondsFormat, Utc}; + use std::fmt; + + use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc}; - use super::*; + use super::{DateTime, FromFixedOffset}; - pub(super) fn to_output(v: &DateTime) -> Value + pub(super) fn to_output(v: &DateTime) -> String where - S: ScalarValue, - Tz: chrono::TimeZone, + Tz: TimeZone, Tz::Offset: fmt::Display, { - Value::scalar( - v.with_timezone(&Utc) - .to_rfc3339_opts(SecondsFormat::AutoSi, true), - ) + v.with_timezone(&Utc) + .to_rfc3339_opts(SecondsFormat::AutoSi, true) // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result, Box> diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index ed38c6d87..213414ad6 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -11,7 +11,7 @@ //! [1]: http://www.iana.org/time-zones //! [s1]: https://graphql-scalars.dev/docs/scalars/time-zone -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; // TODO: Try remove on upgrade of `chrono-tz` crate. mod for_minimal_versions_check_only { @@ -31,7 +31,8 @@ mod for_minimal_versions_check_only { /// [1]: https://graphql-scalars.dev/docs/scalars/time-zone /// [2]: https://docs.rs/chrono-tz/*/chrono_tz/enum.Tz.html /// [3]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = tz, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", @@ -39,10 +40,10 @@ mod for_minimal_versions_check_only { pub type TimeZone = chrono_tz::Tz; mod tz { - use super::*; + use super::TimeZone; - pub(super) fn to_output(v: &TimeZone) -> Value { - Value::scalar(v.name().to_owned()) + pub(super) fn to_output(v: &TimeZone) -> &'static str { + v.name() } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index 045c95f39..d0e11a786 100644 --- a/juniper/src/integrations/jiff.rs +++ b/juniper/src/integrations/jiff.rs @@ -54,7 +54,7 @@ use std::str; use derive_more::with_trait::{Debug, Display, Error, Into}; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Representation of a civil date in the Gregorian calendar. /// @@ -68,7 +68,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Date.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -83,11 +84,8 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDate) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -107,7 +105,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Time.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -132,18 +131,13 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> Value - where - S: ScalarValue, - { - Value::scalar( - if v.subsec_nanosecond() == 0 { - v.strftime(FORMAT_NO_MILLIS) - } else { - v.strftime(FORMAT) - } - .to_string(), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + if v.subsec_nanosecond() == 0 { + v.strftime(FORMAT_NO_MILLIS) + } else { + v.strftime(FORMAT) + } + .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -170,7 +164,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/jiff/*/jiff/civil/struct.DateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -185,11 +180,8 @@ mod local_date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDateTime) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -208,7 +200,8 @@ mod local_date_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/jiff/*/jiff/struct.Timestamp.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -225,11 +218,8 @@ mod date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ"; - pub(super) fn to_output(v: &DateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &DateTime) -> String { + v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -253,7 +243,8 @@ mod date_time { /// [3]: https://docs.rs/jiff/latest/jiff/struct.Timestamp.html /// [4]: https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html /// [5]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = zoned_date_time, parse_token(String), specified_by_url = "https://datatracker.ietf.org/doc/html/rfc9557#section-4.1", @@ -265,11 +256,8 @@ mod zoned_date_time { use super::*; - pub(super) fn to_output(v: &ZonedDateTime) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &ZonedDateTime) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -288,7 +276,8 @@ mod zoned_date_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/duration /// [2]: https://docs.rs/jiff/*/jiff/struct.Span.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = duration, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/duration", @@ -300,11 +289,8 @@ mod duration { use super::*; - pub(super) fn to_output(v: &Duration) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Duration) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -326,7 +312,8 @@ mod duration { /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html /// [3]: https://graphql-scalars.dev/docs/scalars/time-zone /// [4]: https://graphql-scalars.dev/docs/scalars/utc-offset -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = time_zone_or_utc_offset, parse_token(String), )] @@ -338,11 +325,8 @@ mod time_zone_or_utc_offset { /// Format of a [`TimeZoneOrUtcOffset`] scalar. const FORMAT: &str = "%:Q"; - pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> Value - where - S: ScalarValue, - { - Value::scalar(v.iana_name().map_or_else( + pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> String { + v.iana_name().map_or_else( || { // If no IANA time zone identifier is available, fall back to displaying the time // offset directly (using format `[+-]HH:MM[:SS]` from RFC 9557, e.g. `+05:30`). @@ -353,7 +337,7 @@ mod time_zone_or_utc_offset { .to_string() }, ToOwned::to_owned, - )) + ) // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -392,7 +376,8 @@ pub enum TimeZoneParsingError { /// [0]: http://iana.org/time-zones /// [1]: https://graphql-scalars.dev/docs/scalars/time-zone /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = time_zone, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", @@ -425,11 +410,8 @@ impl str::FromStr for TimeZone { mod time_zone { use super::*; - pub(super) fn to_output(v: &TimeZone) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &TimeZone) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -446,7 +428,8 @@ mod time_zone { /// /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset /// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.Offset.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = utc_offset, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", @@ -469,16 +452,13 @@ mod utc_offset { Ok(offset) } - pub(super) fn to_output(v: &UtcOffset) -> Value - where - S: ScalarValue, - { + pub(super) fn to_output(v: &UtcOffset) -> String { let mut buf = String::new(); let tm = jiff::fmt::strtime::BrokenDownTime::from( &jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(*v)), ); tm.format(FORMAT, &mut buf).unwrap(); - Value::scalar(buf) + buf // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index c8952e47a..16e27dc3d 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -8,9 +8,7 @@ //! //! [`Decimal`]: rust_decimal::Decimal -use std::str::FromStr as _; - -use crate::{Scalar, ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// 128 bit representation of a fixed-precision decimal number. /// @@ -26,7 +24,8 @@ use crate::{Scalar, ScalarValue, Value, graphql_scalar}; /// See also [`rust_decimal`] crate for details. /// /// [`rust_decimal`]: https://docs.rs/rust_decimal -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = rust_decimal_scalar, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/rust_decimal", @@ -34,10 +33,11 @@ use crate::{Scalar, ScalarValue, Value, graphql_scalar}; type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { - use super::*; + use super::Decimal; + use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &Decimal) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Decimal) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(v: &Scalar) -> Result> { @@ -50,7 +50,7 @@ mod rust_decimal_scalar { v.try_to::<&str>() .map_err(|e| e.to_string().into()) .and_then(|s| { - Decimal::from_str(s) + s.parse::() .map_err(|e| format!("Failed to parse `Decimal` from `String`: {e}").into()) }) } @@ -59,8 +59,6 @@ mod rust_decimal_scalar { #[cfg(test)] mod test { - use std::str::FromStr as _; - use crate::{FromInputValue as _, InputValue, ToInputValue as _, graphql_input_value}; use super::Decimal; @@ -78,7 +76,7 @@ mod test { ] { let input: InputValue = input; let parsed = Decimal::from_input_value(&input); - let expected = Decimal::from_str(expected).unwrap(); + let expected = expected.parse::().unwrap(); assert!( parsed.is_ok(), @@ -112,7 +110,7 @@ mod test { #[test] fn formats_correctly() { for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { - let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); + let actual: InputValue = raw.parse::().unwrap().to_input_value(); assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}"); } diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index e31ebbfb8..f0c0f9e54 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -27,7 +27,7 @@ use time::{ macros::format_description, }; -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// Date in the proleptic Gregorian calendar (without time zone). /// @@ -40,7 +40,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date /// [2]: https://docs.rs/time/*/time/struct.Date.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date", @@ -55,11 +56,10 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]"); - pub(super) fn to_output(v: &LocalDate) -> Value { - Value::scalar( - v.format(FORMAT) - .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")), - ) + pub(super) fn to_output(v: &LocalDate) -> String { + // TODO: Optimize via `Display`? + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -79,7 +79,8 @@ mod local_date { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/time/*/time/struct.Time.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", @@ -106,15 +107,14 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &[BorrowedFormatItem<'_>] = format_description!("[hour]:[minute]"); - pub(super) fn to_output(v: &LocalTime) -> Value { - Value::scalar( - if v.millisecond() == 0 { - v.format(FORMAT_NO_MILLIS) - } else { - v.format(FORMAT) - } - .unwrap_or_else(|e| panic!("failed to format `LocalTime`: {e}")), - ) + pub(super) fn to_output(v: &LocalTime) -> String { + // TODO: Optimize via `Display`? + if v.millisecond() == 0 { + v.format(FORMAT_NO_MILLIS) + } else { + v.format(FORMAT) + } + .unwrap_or_else(|e| panic!("failed to format `LocalTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -135,7 +135,8 @@ mod local_time { /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time /// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = local_date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time", @@ -151,11 +152,10 @@ mod local_date_time { const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]"); - pub(super) fn to_output(v: &LocalDateTime) -> Value { - Value::scalar( - v.format(FORMAT) - .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")), - ) + pub(super) fn to_output(v: &LocalDateTime) -> String { + // TODO: Optimize via `Display`? + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -175,7 +175,8 @@ mod local_date_time { /// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 /// [1]: https://graphql-scalars.dev/docs/scalars/date-time /// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = date_time, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", @@ -185,12 +186,11 @@ pub type DateTime = time::OffsetDateTime; mod date_time { use super::*; - pub(super) fn to_output(v: &DateTime) -> Value { - Value::scalar( - v.to_offset(UtcOffset::UTC) - .format(&Rfc3339) - .unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}")), - ) + pub(super) fn to_output(v: &DateTime) -> String { + // TODO: Optimize via `Display`? + v.to_offset(UtcOffset::UTC) + .format(&Rfc3339) + .unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -215,7 +215,8 @@ const UTC_OFFSET_FORMAT: &[BorrowedFormatItem<'_>] = /// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones /// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset /// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( with = utc_offset, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", @@ -225,11 +226,10 @@ pub type UtcOffset = time::UtcOffset; mod utc_offset { use super::*; - pub(super) fn to_output(v: &UtcOffset) -> Value { - Value::scalar( - v.format(UTC_OFFSET_FORMAT) - .unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}")), - ) + pub(super) fn to_output(v: &UtcOffset) -> String { + // TODO: Optimize via `Display`? + v.format(UTC_OFFSET_FORMAT) + .unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 8bae4896c..412d67152 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -9,7 +9,7 @@ //! [`Url`]: url::Url //! [s1]: https://graphql-scalars.dev/docs/scalars/url -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// [Standard URL][0] format as specified in [RFC 3986]. /// @@ -21,20 +21,18 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// [1]: https://graphql-scalars.dev/docs/scalars/url /// [2]: https://docs.rs/url/*/url/struct.Url.html /// [RFC 3986]: https://datatracker.ietf.org/doc/html/rfc3986 -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "URL", with = url_scalar, + to_output_with = Url::as_str, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/url", )] type Url = url::Url; mod url_scalar { - use super::*; - - pub(super) fn to_output(v: &Url) -> Value { - Value::scalar(v.as_str().to_owned()) - } + use super::Url; pub(super) fn from_input(s: &str) -> Result> { Url::parse(s).map_err(|e| format!("Failed to parse `URL`: {e}").into()) diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 70ce7c7cb..067b6b589 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -9,7 +9,7 @@ //! [`Uuid`]: uuid::Uuid //! [s1]: https://graphql-scalars.dev/docs/scalars/uuid -use crate::{ScalarValue, Value, graphql_scalar}; +use crate::graphql_scalar; /// [Universally Unique Identifier][0] (UUID). /// @@ -20,7 +20,8 @@ use crate::{ScalarValue, Value, graphql_scalar}; /// [0]: https://en.wikipedia.org/wiki/Universally_unique_identifier /// [1]: https://graphql-scalars.dev/docs/scalars/uuid /// [2]: https://docs.rs/uuid/*/uuid/struct.Uuid.html -#[graphql_scalar( +#[graphql_scalar] +#[graphql( name = "UUID", with = uuid_scalar, parse_token(String), @@ -29,10 +30,10 @@ use crate::{ScalarValue, Value, graphql_scalar}; type Uuid = uuid::Uuid; mod uuid_scalar { - use super::*; + use super::Uuid; - pub(super) fn to_output(v: &Uuid) -> Value { - Value::scalar(v.to_string()) + pub(super) fn to_output(v: &Uuid) -> String { + v.to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 75a414aa2..97bb89081 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -7,7 +7,7 @@ use std::convert::Infallible; use derive_more::with_trait::Display; use futures::future::{self, BoxFuture}; -use crate::{FieldError, InputValue, ScalarValue}; +use crate::{FieldError, InputValue, ScalarValue, ToScalarValue}; /// This trait is used by [`graphql_scalar`] macro to retrieve [`Error`] type from a [`Result`]. /// @@ -94,3 +94,34 @@ impl ToResultCall for &fn(I) -> Result { self(input) } } + +/// [Autoref-based specialized][0] coercion into a [`ScalarValue`] for a function call for providing +/// a return-type polymorphism in macros. +/// +/// [0]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html +pub trait ToScalarValueCall { + /// Input of this function. + type Input; + + /// Calls this function, coercing its output into a [`ScalarValue`]. + fn __to_scalar_value_call(&self, input: Self::Input) -> S; +} + +impl ToScalarValueCall for &fn(I) -> S { + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + self(input) + } +} + +impl ToScalarValueCall for fn(I) -> O +where + O: ToScalarValue, +{ + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + self(input).to_scalar_value() + } +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 3812af629..e98d680e1 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -35,8 +35,8 @@ use crate::{ pub struct ID(Box); impl ID { - fn to_output(&self) -> Value { - Value::scalar(self.0.clone().into_string()) + fn to_output(&self) -> &str { + &self.0 } fn from_input(v: &Scalar) -> Result> { @@ -59,7 +59,11 @@ impl ID { } #[graphql_scalar] -#[graphql(with = impl_string_scalar, from_input_with = __builtin)] +#[graphql( + with = impl_string_scalar, + to_output_with = String::as_str + from_input_with = __builtin, +)] type String = std::string::String; mod impl_string_scalar { @@ -76,19 +80,6 @@ mod impl_string_scalar { } } - impl ToScalarValue for String - where - Self: Into, - { - fn to_scalar_value(&self) -> S { - self.clone().into() - } - } - - pub(super) fn to_output(v: &str) -> Value { - Value::scalar(v.to_owned()) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); @@ -199,10 +190,10 @@ type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { use super::ArcStr; - use crate::{FromScalarValue, IntoValue as _, Scalar, ScalarValue, Value}; + use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &ArcStr) -> Value { - v.into_value() + pub(super) fn to_output(v: &ArcStr) -> S { + S::from_displayable(v) // TODO: Better ergonomics? } pub(super) fn from_input( @@ -222,10 +213,10 @@ type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { use super::CompactString; - use crate::{FromScalarValue, IntoValue as _, Scalar, ScalarValue, Value}; + use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &CompactString) -> Value { - v.into_value() + pub(super) fn to_output(v: &CompactString) -> S { + S::from_displayable(v) // TODO: Better ergonomics? } pub(super) fn from_input( @@ -322,7 +313,7 @@ where str: ToScalarValue, { fn to_input_value(&self) -> InputValue { - InputValue::scalar::(self.to_scalar_value()) + InputValue::Scalar(self.to_scalar_value()) } } @@ -344,17 +335,8 @@ mod impl_boolean_scalar { } } - impl ToScalarValue for Boolean - where - Self: Into, - { - fn to_scalar_value(&self) -> S { - (*self).into() - } - } - - pub(super) fn to_output(v: &Boolean) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Boolean) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -381,17 +363,8 @@ mod impl_int_scalar { } } - impl ToScalarValue for Int - where - Self: Into, - { - fn to_scalar_value(&self) -> S { - (*self).into() - } - } - - pub(super) fn to_output(v: &Int) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Int) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -423,17 +396,8 @@ mod impl_float_scalar { } } - impl ToScalarValue for Float - where - Self: Into, - { - fn to_scalar_value(&self) -> S { - (*self).into() - } - } - - pub(super) fn to_output(v: &Float) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Float) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index f81c25537..be7ae8a90 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -319,6 +319,7 @@ impl ToTokens for Definition { self.impl_type_tokens().to_tokens(into); self.impl_value_tokens().to_tokens(into); self.impl_value_async_tokens().to_tokens(into); + self.impl_to_scalar_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_scalar_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); @@ -402,16 +403,46 @@ impl Definition { fn impl_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let resolve = self.methods.expand_resolve(scalar); + let (ty, mut generics) = self.impl_self_and_generics(false); + + let resolve_body = match &self.methods { + Methods::Custom { .. } + | Methods::Delegated { + to_output: Some(_), .. + } => { + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::core::result::Result::Ok(::juniper::Value::Scalar( + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(self) + )) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::GraphQLValue<#scalar> + }); + + quote! { + ::juniper::GraphQLValue::<#scalar>::resolve( + &self.#field, + info, + selection, + executor, + ) + } + } + }; - let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] - impl #impl_gens ::juniper::GraphQLValue<#scalar> for #ty - #where_clause - { + impl #impl_gens ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = (); type TypeInfo = (); @@ -428,7 +459,7 @@ impl Definition { selection: ::core::option::Option<&[::juniper::Selection<'_, #scalar>]>, executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { - #resolve + #resolve_body } } } @@ -447,9 +478,7 @@ impl Definition { quote! { #[automatically_derived] - impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty - #where_clause - { + impl #impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_async<'b>( &'b self, info: &'b Self::TypeInfo, @@ -463,6 +492,53 @@ impl Definition { } } + /// Returns generated code implementing [`ToScalarValue`] trait for this [GraphQL scalar][1]. + /// + /// [`ToScalarValue`]: juniper::ToScalarValue + /// [1]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_to_scalar_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (ty, mut generics) = self.impl_self_and_generics(false); + + let body = match &self.methods { + Methods::Custom { to_output, .. } + | Methods::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { + use ::juniper::macros::helper::ToScalarValueCall as _; + + let func: fn(_) -> _ = #to_output; + (&&func).__to_scalar_value_call(self) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(&self.#field) + } + } + }; + + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::ToScalarValue<#scalar> for #ty #where_clause { + fn to_scalar_value(&self) -> #scalar { + #body + } + } + } + } + /// Returns generated code implementing [`InputValue`] trait for this /// [GraphQL scalar][1]. /// @@ -471,18 +547,43 @@ impl Definition { fn impl_to_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let to_input_value = self.methods.expand_to_input_value(scalar); + let (ty, mut generics) = self.impl_self_and_generics(false); + + let body = match &self.methods { + Methods::Custom { .. } + | Methods::Delegated { + to_output: Some(_), .. + } => { + generics.make_where_clause().predicates.push(parse_quote! { + Self: ::juniper::ToScalarValue<#scalar> + }); + + quote! { + ::juniper::InputValue::Scalar( + ::juniper::ToScalarValue::<#scalar>::to_scalar_value(self) + ) + } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::ToInputValue<#scalar> + }); + + quote! { + ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) + } + } + }; - let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] - impl #impl_gens ::juniper::ToInputValue<#scalar> for #ty - #where_clause - { + impl #impl_gens ::juniper::ToInputValue<#scalar> for #ty #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - #to_input_value + #body } } } @@ -818,54 +919,6 @@ enum Methods { } impl Methods { - /// Expands [`GraphQLValue::resolve`] method. - /// - /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve - fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { ::core::result::Result::Ok(#to_output(self)) } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::GraphQLValue::<#scalar>::resolve( - &self.#field, - info, - selection, - executor, - ) - } - } - } - } - - /// Expands [`ToInputValue::to_input_value`] method. - /// - /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value - fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { - let v = #to_output(self); - ::juniper::ToInputValue::to_input_value(&v) - } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) - } - } - } - } - /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str From fd740a06a938d61f02e49a27cf4432bd115351ca Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 24 Jun 2025 16:57:41 +0300 Subject: [PATCH 05/14] Improve ergonomics, vol.1 --- juniper/src/integrations/bigdecimal.rs | 7 +-- juniper/src/integrations/chrono.rs | 19 +++++--- juniper/src/integrations/jiff.rs | 53 +++++++++-------------- juniper/src/integrations/rust_decimal.rs | 7 +-- juniper/src/integrations/time.rs | 5 --- juniper/src/integrations/uuid.rs | 7 +-- juniper/src/macros/helper/mod.rs | 31 ++++++++++++- juniper/src/types/scalars.rs | 22 +++++----- juniper/src/value/scalar.rs | 5 +++ juniper_codegen/src/graphql_scalar/mod.rs | 4 +- 10 files changed, 86 insertions(+), 74 deletions(-) diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index b4b0bf5f2..27e1ea015 100644 --- a/juniper/src/integrations/bigdecimal.rs +++ b/juniper/src/integrations/bigdecimal.rs @@ -8,7 +8,7 @@ //! //! [`BigDecimal`]: bigdecimal::BigDecimal -use crate::graphql_scalar; +use crate::{ScalarValue, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -30,6 +30,7 @@ mod for_minimal_versions_check_only { #[graphql_scalar] #[graphql( with = bigdecimal_scalar, + to_output_with = ScalarValue::from_displayable, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/bigdecimal", )] @@ -39,10 +40,6 @@ mod bigdecimal_scalar { use super::BigDecimal; use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &BigDecimal) -> String { - v.to_string() // TODO: Optimize via `Display`? - } - pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { Ok(BigDecimal::from(i)) diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index ec437c677..114c55a18 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -45,6 +45,8 @@ use crate::graphql_scalar; pub type LocalDate = chrono::NaiveDate; mod local_date { + use std::fmt::Display; + use super::LocalDate; /// Format of a [`LocalDate` scalar][1]. @@ -52,8 +54,8 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> String { - v.format(FORMAT).to_string() // TODO: Optimize via `Display`? + pub(super) fn to_output(v: &LocalDate) -> impl Display { + v.format(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -82,6 +84,8 @@ mod local_date { pub type LocalTime = chrono::NaiveTime; mod local_time { + use std::fmt::Display; + use chrono::Timelike as _; use super::LocalTime; @@ -101,13 +105,12 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> String { + pub(super) fn to_output(v: &LocalTime) -> impl Display { if v.nanosecond() == 0 { v.format(FORMAT_NO_MILLIS) } else { v.format(FORMAT) } - .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -137,6 +140,8 @@ mod local_time { pub type LocalDateTime = chrono::NaiveDateTime; mod local_date_time { + use std::fmt::Display; + use super::LocalDateTime; /// Format of a [`LocalDateTime` scalar][1]. @@ -144,8 +149,8 @@ mod local_date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> String { - v.format(FORMAT).to_string() // TODO: Optimize via `Display`? + pub(super) fn to_output(v: &LocalDateTime) -> impl Display { + v.format(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -191,7 +196,7 @@ mod date_time { Tz::Offset: fmt::Display, { v.with_timezone(&Utc) - .to_rfc3339_opts(SecondsFormat::AutoSi, true) // TODO: Optimize via `Display`? + .to_rfc3339_opts(SecondsFormat::AutoSi, true) } pub(super) fn from_input(s: &str) -> Result, Box> diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index d0e11a786..8103694ff 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::graphql_scalar; +use crate::{ScalarValue, graphql_scalar}; /// Representation of a civil date in the Gregorian calendar. /// @@ -84,8 +84,8 @@ mod local_date { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date const FORMAT: &str = "%Y-%m-%d"; - pub(super) fn to_output(v: &LocalDate) -> String { - v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? + pub(super) fn to_output(v: &LocalDate) -> impl Display { + v.strftime(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -131,13 +131,12 @@ mod local_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const FORMAT_NO_SECS: &str = "%H:%M"; - pub(super) fn to_output(v: &LocalTime) -> String { + pub(super) fn to_output(v: &LocalTime) -> impl Display { if v.subsec_nanosecond() == 0 { v.strftime(FORMAT_NO_MILLIS) } else { v.strftime(FORMAT) } - .to_string() // TODO: Optimize via `Display`? } pub(super) fn from_input(s: &str) -> Result> { @@ -180,8 +179,8 @@ mod local_date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; - pub(super) fn to_output(v: &LocalDateTime) -> String { - v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? + pub(super) fn to_output(v: &LocalDateTime) -> impl Display { + v.strftime(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -209,8 +208,6 @@ mod local_date_time { pub type DateTime = jiff::Timestamp; mod date_time { - use std::str::FromStr as _; - use super::*; /// Format of a [`DateTime` scalar][1]. @@ -218,12 +215,13 @@ mod date_time { /// [1]: https://graphql-scalars.dev/docs/scalars/date-time const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ"; - pub(super) fn to_output(v: &DateTime) -> String { - v.strftime(FORMAT).to_string() // TODO: Optimize via `Display`? + pub(super) fn to_output(v: &DateTime) -> impl Display { + v.strftime(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { - DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}").into()) + s.parse() + .map_err(|e| format!("Invalid `DateTime`: {e}").into()) } } @@ -246,22 +244,18 @@ mod date_time { #[graphql_scalar] #[graphql( with = zoned_date_time, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://datatracker.ietf.org/doc/html/rfc9557#section-4.1", )] pub type ZonedDateTime = jiff::Zoned; mod zoned_date_time { - use std::str::FromStr as _; - - use super::*; - - pub(super) fn to_output(v: &ZonedDateTime) -> String { - v.to_string() // TODO: Optimize via `Display`? - } + use super::ZonedDateTime; pub(super) fn from_input(s: &str) -> Result> { - ZonedDateTime::from_str(s).map_err(|e| format!("Invalid `ZonedDateTime`: {e}").into()) + s.parse() + .map_err(|e| format!("Invalid `ZonedDateTime`: {e}").into()) } } @@ -279,22 +273,18 @@ mod zoned_date_time { #[graphql_scalar] #[graphql( with = duration, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/duration", )] pub type Duration = jiff::Span; mod duration { - use std::str::FromStr as _; - - use super::*; - - pub(super) fn to_output(v: &Duration) -> String { - v.to_string() // TODO: Optimize via `Display`? - } + use super::Duration; pub(super) fn from_input(s: &str) -> Result> { - Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}").into()) + s.parse() + .map_err(|e| format!("Invalid `Duration`: {e}").into()) } } @@ -379,6 +369,7 @@ pub enum TimeZoneParsingError { #[graphql_scalar] #[graphql( with = time_zone, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", )] @@ -408,11 +399,7 @@ impl str::FromStr for TimeZone { } mod time_zone { - use super::*; - - pub(super) fn to_output(v: &TimeZone) -> String { - v.to_string() // TODO: Optimize via `Display`? - } + use super::TimeZone; pub(super) fn from_input(s: &str) -> Result> { s.parse() diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs index 16e27dc3d..f333962a8 100644 --- a/juniper/src/integrations/rust_decimal.rs +++ b/juniper/src/integrations/rust_decimal.rs @@ -8,7 +8,7 @@ //! //! [`Decimal`]: rust_decimal::Decimal -use crate::graphql_scalar; +use crate::{ScalarValue, graphql_scalar}; /// 128 bit representation of a fixed-precision decimal number. /// @@ -27,6 +27,7 @@ use crate::graphql_scalar; #[graphql_scalar] #[graphql( with = rust_decimal_scalar, + to_output_with = ScalarValue::from_displayable, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/rust_decimal", )] @@ -36,10 +37,6 @@ mod rust_decimal_scalar { use super::Decimal; use crate::{Scalar, ScalarValue}; - pub(super) fn to_output(v: &Decimal) -> String { - v.to_string() // TODO: Optimize via `Display`? - } - pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { Ok(Decimal::from(i)) diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index f0c0f9e54..9a7f889a6 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -57,7 +57,6 @@ mod local_date { const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]"); pub(super) fn to_output(v: &LocalDate) -> String { - // TODO: Optimize via `Display`? v.format(FORMAT) .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")) } @@ -108,7 +107,6 @@ mod local_time { const FORMAT_NO_SECS: &[BorrowedFormatItem<'_>] = format_description!("[hour]:[minute]"); pub(super) fn to_output(v: &LocalTime) -> String { - // TODO: Optimize via `Display`? if v.millisecond() == 0 { v.format(FORMAT_NO_MILLIS) } else { @@ -153,7 +151,6 @@ mod local_date_time { format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]"); pub(super) fn to_output(v: &LocalDateTime) -> String { - // TODO: Optimize via `Display`? v.format(FORMAT) .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")) } @@ -187,7 +184,6 @@ mod date_time { use super::*; pub(super) fn to_output(v: &DateTime) -> String { - // TODO: Optimize via `Display`? v.to_offset(UtcOffset::UTC) .format(&Rfc3339) .unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}")) @@ -227,7 +223,6 @@ mod utc_offset { use super::*; pub(super) fn to_output(v: &UtcOffset) -> String { - // TODO: Optimize via `Display`? v.format(UTC_OFFSET_FORMAT) .unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}")) } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 067b6b589..cf5bee540 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::graphql_scalar; +use crate::{ScalarValue, graphql_scalar}; /// [Universally Unique Identifier][0] (UUID). /// @@ -24,6 +24,7 @@ use crate::graphql_scalar; #[graphql( name = "UUID", with = uuid_scalar, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/uuid", )] @@ -32,10 +33,6 @@ type Uuid = uuid::Uuid; mod uuid_scalar { use super::Uuid; - pub(super) fn to_output(v: &Uuid) -> String { - v.to_string() // TODO: Optimize via `Display`? - } - 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/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 97bb89081..9468d648a 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -107,7 +107,10 @@ pub trait ToScalarValueCall { fn __to_scalar_value_call(&self, input: Self::Input) -> S; } -impl ToScalarValueCall for &fn(I) -> S { +impl ToScalarValueCall for &&&fn(I) -> S +where + S: ScalarValue, +{ type Input = I; fn __to_scalar_value_call(&self, input: Self::Input) -> S { @@ -115,8 +118,20 @@ impl ToScalarValueCall for &fn(I) -> S { } } -impl ToScalarValueCall for fn(I) -> O +impl ToScalarValueCall for &&fn(I) -> String +where + S: ScalarValue, +{ + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + self(input).into() + } +} + +impl ToScalarValueCall for &fn(I) -> O where + S: ScalarValue, O: ToScalarValue, { type Input = I; @@ -125,3 +140,15 @@ where self(input).to_scalar_value() } } + +impl ToScalarValueCall for fn(I) -> O +where + S: ScalarValue, + O: Display, +{ + type Input = I; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + S::from_display(&self(input)) + } +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index e98d680e1..4d337595d 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -185,17 +185,18 @@ where } #[graphql_scalar] -#[graphql(name = "String", with = impl_arcstr_scalar, parse_token(String))] +#[graphql( + name = "String", + with = impl_arcstr_scalar, + to_output_with = ScalarValue::from_displayable, + parse_token(String) +)] type ArcStr = arcstr::ArcStr; mod impl_arcstr_scalar { use super::ArcStr; use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &ArcStr) -> S { - S::from_displayable(v) // TODO: Better ergonomics? - } - pub(super) fn from_input( v: &Scalar, ) -> Result>::Error> { @@ -208,17 +209,18 @@ mod impl_arcstr_scalar { } #[graphql_scalar] -#[graphql(name = "String", with = impl_compactstring_scalar, parse_token(String))] +#[graphql( + name = "String", + with = impl_compactstring_scalar, + to_output_with = ScalarValue::from_displayable, + parse_token(String), +)] type CompactString = compact_str::CompactString; mod impl_compactstring_scalar { use super::CompactString; use crate::{FromScalarValue, Scalar, ScalarValue}; - pub(super) fn to_output(v: &CompactString) -> S { - S::from_displayable(v) // TODO: Better ergonomics? - } - pub(super) fn from_input( v: &Scalar, ) -> Result>::Error> { diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 5a3a437ae..d5c1bf2ec 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -488,6 +488,11 @@ pub trait ScalarValue: fn from_displayable(s: &Str) -> Self { s.to_string().into() } + + #[must_use] + fn from_display(s: &Str) -> Self { + s.to_string().into() + } } /// Fallible representation of a [`ScalarValue`] as one of the types it consists of, or derived ones diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index be7ae8a90..a7d4d8e82 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -424,7 +424,7 @@ impl Definition { let field_ty = field.ty(); generics.make_where_clause().predicates.push(parse_quote! { - #field_ty: ::juniper::GraphQLValue<#scalar> + #field_ty: ::juniper::GraphQLValue<#scalar, Context = (), TypeInfo = ()> }); quote! { @@ -511,7 +511,7 @@ impl Definition { use ::juniper::macros::helper::ToScalarValueCall as _; let func: fn(_) -> _ = #to_output; - (&&func).__to_scalar_value_call(self) + (&&&&func).__to_scalar_value_call(self) } } Methods::Delegated { field, .. } => { From ea5f9faf2d00181b3f421dd1e692606050cd8822 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 16:52:32 +0300 Subject: [PATCH 06/14] Polish `from_displayable_non_static()` conversion --- juniper/Cargo.toml | 1 + juniper/src/integrations/chrono.rs | 4 +- juniper/src/integrations/jiff.rs | 4 +- juniper/src/macros/helper/mod.rs | 14 +- juniper/src/types/scalars.rs | 2 +- juniper/src/value/scalar.rs | 152 ++++++++++++++++++++-- juniper_codegen/src/graphql_scalar/mod.rs | 55 ++++---- juniper_codegen/src/scalar_value/mod.rs | 30 +++++ 8 files changed, 212 insertions(+), 50 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 5b12430ac..e07013e20 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -80,6 +80,7 @@ tap = { version = "1.0.1", optional = true } void = { version = "1.0.2", optional = true } [dev-dependencies] +arcstr = { version = "1.1", features = ["serde"] } bencher = "0.1.2" chrono = { version = "0.4.30", features = ["alloc"], default-features = false } compact_str = { version = "0.9", features = ["serde"] } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 114c55a18..4e27b41f1 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -184,7 +184,7 @@ mod local_date_time { pub type DateTime = chrono::DateTime; mod date_time { - use std::fmt; + use std::fmt::Display; use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc}; @@ -193,7 +193,7 @@ mod date_time { pub(super) fn to_output(v: &DateTime) -> String where Tz: TimeZone, - Tz::Offset: fmt::Display, + Tz::Offset: Display, { v.with_timezone(&Utc) .to_rfc3339_opts(SecondsFormat::AutoSi, true) diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs index 8103694ff..f434b9404 100644 --- a/juniper/src/integrations/jiff.rs +++ b/juniper/src/integrations/jiff.rs @@ -327,7 +327,7 @@ mod time_zone_or_utc_offset { .to_string() }, ToOwned::to_owned, - ) // TODO: Optimize via `Display`? + ) } pub(super) fn from_input(s: &str) -> Result> { @@ -445,7 +445,7 @@ mod utc_offset { &jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(*v)), ); tm.format(FORMAT, &mut buf).unwrap(); - buf // TODO: Optimize via `Display`? + buf } pub(super) fn from_input(s: &str) -> Result> { diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 9468d648a..a44065b75 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -75,23 +75,23 @@ pub trait ToResultCall { fn __to_result_call(&self, input: Self::Input) -> Result; } -impl ToResultCall for fn(I) -> O { +impl ToResultCall for &fn(I) -> Result { type Input = I; type Output = O; - type Error = Infallible; + type Error = E; fn __to_result_call(&self, input: Self::Input) -> Result { - Ok(self(input)) + self(input) } } -impl ToResultCall for &fn(I) -> Result { +impl ToResultCall for fn(I) -> O { type Input = I; type Output = O; - type Error = E; + type Error = Infallible; fn __to_result_call(&self, input: Self::Input) -> Result { - self(input) + Ok(self(input)) } } @@ -149,6 +149,6 @@ where type Input = I; fn __to_scalar_value_call(&self, input: Self::Input) -> S { - S::from_display(&self(input)) + S::from_displayable_non_static(&self(input)) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 4d337595d..aaf72df7b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -312,7 +312,7 @@ impl ToScalarValue for str { impl ToInputValue for str where - str: ToScalarValue, + Self: ToScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::Scalar(self.to_scalar_value()) diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index d5c1bf2ec..440a12a83 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -476,22 +476,158 @@ pub trait ScalarValue: /// Creates this [`ScalarValue`] from the provided [`Display`]able type. /// - /// This method should be implemented if [`ScalarValue`] implementation uses some custom string - /// type inside to enable efficient conversion from values of this type. + /// # Usage + /// + /// This method cannot work with non-`'static` types due to [`Any`] `'static` restriction. For + /// non-`'static` types the [`ScalarValue::from_displayable_non_static()`] method should be used + /// instead. However, the [`Any`] here allows implementors to specialize some conversions to be + /// cheaper for their [`ScalarValue`] implementation, and so, using this method is preferred + /// whenever is possible. + /// + /// # Implementation /// /// Default implementation allocates by converting [`ToString`] and [`From`]. /// - /// # Example + /// This method should be implemented if [`ScalarValue`] implementation uses some custom string + /// type inside to enable efficient conversion from values of this type. /// - /// See the [example in trait documentation](ScalarValue#example) for how it can be used. + /// ```rust + /// # use std::any::Any; + /// # + /// use arcstr::ArcStr; + /// use derive_more::with_trait::{Display, From, TryInto}; + /// use juniper::ScalarValue; + /// use serde::{Deserialize, Serialize}; + /// + /// #[derive( + /// Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + /// )] + /// #[serde(untagged)] + /// #[value(from_displayable_with = from_arcstr)] + /// enum MyScalarValue { + /// #[from] + /// #[value(to_float, to_int)] + /// Int(i32), + /// + /// #[from] + /// #[value(to_float)] + /// Float(f64), + /// + /// #[from(&str, String, ArcStr)] + /// #[value(as_str, to_string)] + /// String(ArcStr), + /// + /// #[from] + /// #[value(to_bool)] + /// Boolean(bool), + /// } + /// + /// // Custom implementation of `ScalarValue::from_displayable()` method for specializing + /// // an efficient conversions from `ArcStr` into `MyScalarValue`. + /// fn from_arcstr(s: &Str) -> MyScalarValue { + /// use juniper::AnyExt as _; // allows downcasting directly on types without `dyn` + /// + /// if let Some(s) = s.downcast_ref::() { + /// MyScalarValue::String(s.clone()) // `Clone`ing `ArcStr` is cheap + /// } else { + /// // We do not override `ScalarValue::from_displayable_non_static()` here, + /// // since `arcstr` crate doesn't provide API for efficient conversion into + /// // an `ArcStr` for any `Display`able type, unfortunately. + /// // The closest possible way is to use `arcstr::format!("{s}")` expression. + /// // However, it actually expands to `ArcStr::from(fmt::format(format_args!("{s}")))`, + /// // where `fmt::format()` allocates a `String`, and thus, is fully equivalent to the + /// // default implementation, which does `.to_string().into()` conversion. + /// MyScalarValue::from_displayable_non_static(s) + /// } + /// } + /// # + /// # // `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.to_string()) + /// # } else { + /// # Err(value) + /// # } + /// # } + /// # } + /// ``` #[must_use] - fn from_displayable(s: &Str) -> Self { - s.to_string().into() + fn from_displayable(value: &T) -> Self { + Self::from_displayable_non_static(value) } + /// Creates this [`ScalarValue`] from the provided non-`'static` [`Display`]able type. + /// + /// # Usage + /// + /// This method exists solely because [`Any`] requires `'static`, and so the + /// [`ScalarValue::from_displayable()`] method cannot cover non-`'static` types. Always prefer + /// to use the [`ScalarValue::from_displayable()`] method instead of this one, whenever it's + /// possible, to allow possible cheap conversion specialization. + /// + /// # Implementation + /// + /// Default implementation allocates by converting [`ToString`] and [`From`]. + /// + /// This method should be implemented if [`ScalarValue`] implementation uses some custom string + /// type inside to create its values efficiently without intermediate [`String`]-conversion. + /// + /// ```rust + /// use compact_str::{CompactString, ToCompactString as _}; + /// use derive_more::with_trait::{Display, From, TryInto}; + /// use juniper::ScalarValue; + /// use serde::{Deserialize, Serialize}; + /// + /// #[derive( + /// Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto, + /// )] + /// #[serde(untagged)] + /// #[value(from_displayable_non_static_with = to_compact_string)] + /// enum MyScalarValue { + /// #[from] + /// #[value(to_float, to_int)] + /// Int(i32), + /// + /// #[from] + /// #[value(to_float)] + /// Float(f64), + /// + /// #[from(&str, String, CompactString)] + /// #[value(as_str, to_string)] + /// String(CompactString), + /// + /// #[from] + /// #[value(to_bool)] + /// Boolean(bool), + /// } + /// + /// // Custom implementation of `ScalarValue::from_displayable_non_static()` method + /// // for efficient writing into a `CompactString` as a `MyScalarValue::String`. + /// fn to_compact_string(v: &T) -> MyScalarValue { + /// v.to_compact_string().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) + /// # } + /// # } + /// # } + /// ``` #[must_use] - fn from_display(s: &Str) -> Self { - s.to_string().into() + fn from_displayable_non_static(value: &T) -> Self { + value.to_string().into() } } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index a7d4d8e82..7d9c463e7 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -732,20 +732,39 @@ impl Definition { fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let from_str = self.methods.expand_parse_scalar_value(scalar); + let (ty, mut generics) = self.impl_self_and_generics(false); + + let body = match &self.methods { + Methods::Custom { parse_token, .. } + | Methods::Delegated { + parse_token: Some(parse_token), + .. + } => { + let parse_token = parse_token.expand_from_str(scalar); + quote! { #parse_token } + } + Methods::Delegated { field, .. } => { + let field_ty = field.ty(); + + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::ParseScalarValue<#scalar> + }); + + quote! { + <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) + } + } + }; - let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { #[automatically_derived] - impl #impl_gens ::juniper::ParseScalarValue<#scalar> for #ty - #where_clause - { + impl #impl_gens ::juniper::ParseScalarValue<#scalar> for #ty #where_clause { fn from_str( token: ::juniper::parser::ScalarToken<'_>, ) -> ::juniper::ParseScalarResult<#scalar> { - #from_str + #body } } } @@ -918,30 +937,6 @@ enum Methods { }, } -impl Methods { - /// Expands [`ParseScalarValue::from_str`] method. - /// - /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str - fn expand_parse_scalar_value(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { parse_token, .. } - | Self::Delegated { - parse_token: Some(parse_token), - .. - } => { - let parse_token = parse_token.expand_from_str(scalar); - quote! { #parse_token } - } - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { - <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) - } - } - } - } -} - /// Representation of [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str diff --git a/juniper_codegen/src/scalar_value/mod.rs b/juniper_codegen/src/scalar_value/mod.rs index dfbe4729f..48745192d 100644 --- a/juniper_codegen/src/scalar_value/mod.rs +++ b/juniper_codegen/src/scalar_value/mod.rs @@ -52,6 +52,9 @@ pub fn expand_derive(input: TokenStream) -> syn::Result { variants: data_enum.variants.into_iter().collect(), methods, from_displayable: attr.from_displayable.map(SpanContainer::into_inner), + from_displayable_non_static: attr + .from_displayable_non_static + .map(SpanContainer::into_inner), } .into_token_stream()) } @@ -63,6 +66,10 @@ struct Attr { /// Explicitly specified function to be used as `ScalarValue::from_displayable()` /// implementation. from_displayable: Option>, + + /// Explicitly specified function to be used as `ScalarValue::from_displayable_non_static()` + /// implementation. + from_displayable_non_static: Option>, } impl Parse for Attr { @@ -78,6 +85,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "from_displayable_non_static_with" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_displayable_non_static + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } name => { return Err(err::unknown_arg(&ident, name)); } @@ -94,6 +108,7 @@ impl Attr { fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { from_displayable: try_merge_opt!(from_displayable: self, another), + from_displayable_non_static: try_merge_opt!(from_displayable_non_static: self, another), }) } @@ -201,6 +216,11 @@ struct Definition { /// /// If [`None`] then `ScalarValue::from_displayable()` method is not generated. from_displayable: Option, + + /// Custom definition to call in `ScalarValue::from_displayable_non_static()` method. + /// + /// If [`None`] then `ScalarValue::from_displayable_non_static()` method is not generated. + from_displayable_non_static: Option, } impl ToTokens for Definition { @@ -247,6 +267,15 @@ impl Definition { } } }); + let from_displayable_non_static = self.from_displayable_non_static.as_ref().map(|expr| { + quote! { + fn from_displayable_non_static< + __T: ::core::fmt::Display + ?::core::marker::Sized, + >(__v: &__T) -> Self { + #expr(__v) + } + } + }); quote! { #[automatically_derived] @@ -264,6 +293,7 @@ impl Definition { } #from_displayable + #from_displayable_non_static } } } From e7984a808b8b48377da5ee3427c5c82eeb52db25 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 18:02:00 +0300 Subject: [PATCH 07/14] Fix tests --- juniper/src/value/scalar.rs | 4 +- .../scalar/type_alias/attr_invalid_url.rs | 6 +- .../type_alias/attr_with_not_all_resolvers.rs | 6 +- .../tests/codegen_scalar_attr_derive_input.rs | 64 +++++++++---------- .../tests/codegen_scalar_attr_type_alias.rs | 56 ++++++++-------- .../tests/codegen_scalar_derive.rs | 64 +++++++++---------- tests/integration/tests/custom_scalar.rs | 4 +- 7 files changed, 99 insertions(+), 105 deletions(-) diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 440a12a83..879449030 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -522,7 +522,7 @@ pub trait ScalarValue: /// Boolean(bool), /// } /// - /// // Custom implementation of `ScalarValue::from_displayable()` method for specializing + /// // Custom implementation of `ScalarValue::from_displayable()` method for specializing /// // an efficient conversions from `ArcStr` into `MyScalarValue`. /// fn from_arcstr(s: &Str) -> MyScalarValue { /// use juniper::AnyExt as _; // allows downcasting directly on types without `dyn` @@ -531,7 +531,7 @@ pub trait ScalarValue: /// MyScalarValue::String(s.clone()) // `Clone`ing `ArcStr` is cheap /// } else { /// // We do not override `ScalarValue::from_displayable_non_static()` here, - /// // since `arcstr` crate doesn't provide API for efficient conversion into + /// // since `arcstr` crate doesn't provide API for efficient conversion into /// // an `ArcStr` for any `Display`able type, unfortunately. /// // The closest possible way is to use `arcstr::format!("{s}")` expression. /// // However, it actually expands to `ArcStr::from(fmt::format(format_args!("{s}")))`, 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 966576843..7c2e153fd 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,4 @@ -use juniper::{graphql_scalar, Scalar, ScalarValue, Value}; +use juniper::{graphql_scalar, Scalar, ScalarValue}; struct ScalarSpecifiedByUrl; @@ -13,8 +13,8 @@ type MyScalar = ScalarSpecifiedByUrl; mod scalar { use super::*; - pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { - Value::scalar(0) + pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> i32 { + 0 } pub(super) fn from_input(_: &Scalar) -> ScalarSpecifiedByUrl { 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 be209fa03..ccfbedff2 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 @@ -1,4 +1,4 @@ -use juniper::{graphql_scalar, Value}; +use juniper::graphql_scalar; struct Scalar; @@ -7,8 +7,8 @@ struct Scalar; type CustomScalar = Scalar; impl Scalar { - fn to_output(&self) -> Value { - Value::scalar(0) + fn to_output(&self) -> i32 { + 0 } } diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index b1422e656..1014adec9 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::{ - ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, execute, - graphql_object, graphql_scalar, graphql_value, graphql_vars, + ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, execute, graphql_object, + graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ @@ -27,8 +27,8 @@ mod trivial { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -166,8 +166,8 @@ mod transparent_with_resolver { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0 + 1) + fn to_output(&self) -> i32 { + self.0 + 1 } } @@ -236,8 +236,8 @@ mod all_custom_resolvers { #[graphql(parse_token_with = parse_token)] struct Counter(i32); - fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + fn to_output(v: &Counter) -> i32 { + v.0 } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -306,8 +306,8 @@ mod explicit_name { struct CustomCounter(i32); impl CustomCounter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -381,8 +381,8 @@ mod delegated_parse_token { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -455,10 +455,10 @@ mod multiple_delegated_parse_token { } impl StringOrInt { - fn to_output(&self) -> Value { + fn to_output(&self) -> S { match self { - Self::String(s) => Value::scalar(s.to_owned()), - Self::Int(i) => Value::scalar(*i), + Self::String(s) => S::from_displayable(s), + Self::Int(i) => (*i).into(), } } @@ -517,13 +517,12 @@ mod where_attribute { )] struct CustomDateTime(DateTime); - fn to_output(v: &CustomDateTime) -> Value + fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } fn from_input(s: &str) -> prelude::Result, prelude::Box> @@ -588,8 +587,8 @@ mod with_self { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -670,13 +669,12 @@ mod with_module { mod custom_date_time { use super::*; - pub(super) fn to_output(v: &CustomDateTime) -> Value + pub(super) fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } pub(super) fn from_input( @@ -745,8 +743,8 @@ mod description_from_doc_comment { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -820,8 +818,8 @@ mod description_from_attribute { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -895,8 +893,8 @@ mod custom_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -970,8 +968,8 @@ mod generic_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -1044,8 +1042,8 @@ mod bounded_generic_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index a29716a67..0f695a292 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::{ - ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, execute, - graphql_object, graphql_scalar, graphql_value, graphql_vars, + ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, execute, graphql_object, + graphql_scalar, graphql_value, graphql_vars, }; use self::common::{ @@ -33,8 +33,8 @@ mod all_custom_resolvers { )] type Counter = CustomCounter; - fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + fn to_output(v: &Counter) -> i32 { + v.0 } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -109,8 +109,8 @@ mod explicit_name { )] type CounterScalar = CustomCounter; - fn to_output(v: &CounterScalar) -> Value { - Value::scalar(v.0) + fn to_output(v: &CounterScalar) -> i32 { + v.0 } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -204,8 +204,8 @@ mod delegated_parse_token { )] type Counter = CustomCounter; - fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + fn to_output(v: &Counter) -> i32 { + v.0 } struct QueryRoot; @@ -278,10 +278,10 @@ mod multiple_delegated_parse_token { )] type StringOrInt = StringOrIntScalar; - fn to_output(v: &StringOrInt) -> Value { + fn to_output(v: &StringOrInt) -> S { match v { - StringOrInt::String(s) => Value::scalar(s.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + StringOrInt::String(s) => S::from_displayable(s), + StringOrInt::Int(i) => (*i).into(), } } @@ -341,13 +341,12 @@ mod where_attribute { )] type CustomDateTime = CustomDateTimeScalar; - fn to_output(v: &CustomDateTime) -> Value + fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } fn from_input(s: &str) -> prelude::Result, prelude::Box> @@ -414,8 +413,8 @@ mod with_self { type Counter = CustomCounter; impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -498,13 +497,12 @@ mod with_module { mod custom_date_time { use super::*; - pub(super) fn to_output(v: &CustomDateTime) -> Value + pub(super) fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } pub(super) fn from_input( @@ -577,8 +575,8 @@ mod description_from_doc_comment { mod counter { use super::*; - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + pub(super) fn to_output(v: &Counter) -> i32 { + v.0 } pub(super) fn from_input(i: i32) -> Counter { @@ -660,8 +658,8 @@ mod description_from_attribute { mod counter { use super::*; - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + pub(super) fn to_output(v: &Counter) -> i32 { + v.0 } pub(super) fn from_input(i: i32) -> Counter { @@ -743,8 +741,8 @@ mod custom_scalar { mod counter { use super::*; - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + pub(super) fn to_output(v: &Counter) -> i32 { + v.0 } pub(super) fn from_input(i: i32) -> Counter { @@ -826,8 +824,8 @@ mod generic_scalar { mod counter { use super::*; - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + pub(super) fn to_output(v: &Counter) -> i32 { + v.0 } pub(super) fn from_input(i: i32) -> Counter { @@ -909,8 +907,8 @@ mod bounded_generic_scalar { mod counter { use super::*; - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + pub(super) fn to_output(v: &Counter) -> i32 { + v.0 } pub(super) fn from_input(i: i32) -> Counter { diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index dfd0a5b96..ae37557e4 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, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, - execute, graphql_object, graphql_value, graphql_vars, + GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, execute, + graphql_object, graphql_value, graphql_vars, }; use self::common::{ @@ -25,8 +25,8 @@ mod trivial { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -161,8 +161,8 @@ mod transparent_with_resolver { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0 + 1) + fn to_output(&self) -> i32 { + self.0 + 1 } } @@ -231,8 +231,8 @@ mod all_custom_resolvers { #[graphql(parse_token_with = parse_token)] struct Counter(i32); - fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + fn to_output(v: &Counter) -> i32 { + v.0 } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -301,8 +301,8 @@ mod explicit_name { struct CustomCounter(i32); impl CustomCounter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -376,8 +376,8 @@ mod delegated_parse_token { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -450,10 +450,10 @@ mod multiple_delegated_parse_token { } impl StringOrInt { - fn to_output(&self) -> Value { + fn to_output(&self) -> S { match self { - Self::String(s) => Value::scalar(s.to_owned()), - Self::Int(i) => Value::scalar(*i), + Self::String(s) => S::from_displayable(s), + Self::Int(i) => (*i).into(), } } @@ -512,13 +512,12 @@ mod where_attribute { )] struct CustomDateTime(DateTime); - fn to_output(v: &CustomDateTime) -> Value + fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } fn from_input(s: &str) -> prelude::Result, prelude::Box> @@ -583,8 +582,8 @@ mod with_self { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -665,13 +664,12 @@ mod with_module { mod custom_date_time { use super::*; - pub(super) fn to_output(v: &CustomDateTime) -> Value + pub(super) fn to_output(v: &CustomDateTime) -> prelude::String where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.0.to_rfc3339()) + v.0.to_rfc3339() } pub(super) fn from_input( @@ -740,8 +738,8 @@ mod description_from_doc_comment { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -815,8 +813,8 @@ mod description_from_attribute { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -890,8 +888,8 @@ mod custom_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -965,8 +963,8 @@ mod generic_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { @@ -1039,8 +1037,8 @@ mod bounded_generic_scalar { struct Counter(i32); impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) + fn to_output(&self) -> i32 { + self.0 } fn from_input(i: i32) -> Self { diff --git a/tests/integration/tests/custom_scalar.rs b/tests/integration/tests/custom_scalar.rs index 1f6b7c4d6..9dba74b50 100644 --- a/tests/integration/tests/custom_scalar.rs +++ b/tests/integration/tests/custom_scalar.rs @@ -18,8 +18,8 @@ type Long = i64; mod long { use super::*; - pub(super) fn to_output(v: &Long) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Long) -> MyScalarValue { + (*v).into() } pub(super) fn from_input(s: &MyScalarValue) -> Result> { From 6a50f8e50269285d9744e443ce3a3f05f789b0c6 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 18:20:20 +0300 Subject: [PATCH 08/14] Fix doc tests --- juniper/src/value/scalar.rs | 53 ++++++++++--------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 879449030..4f328d036 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -47,67 +47,33 @@ pub trait ParseScalarValue { /// The preferred way to define a new [`ScalarValue`] representation is defining an enum containing /// a variant for each type that needs to be represented at the lowest level. /// -/// The following example introduces a new variant that is able to store 64-bit integers, and uses -/// a [`CompactString`] for a string representation. +/// The following example introduces a new variant that is able to store 64-bit integers. /// /// ```rust /// # use std::{any::Any, fmt}; /// # -/// # use compact_str::CompactString; /// use derive_more::with_trait::{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 { -/// #[from] /// #[value(to_float, to_int)] /// Int(i32), /// -/// #[from] /// Long(i64), -/// -/// #[from] +/// /// #[value(to_float)] /// Float(f64), /// -/// #[from(&str, String, CompactString)] /// #[value(as_str, to_string)] -/// String(CompactString), -/// -/// #[from] +/// String(String), +/// /// #[value(to_bool)] /// Boolean(bool), /// } /// -/// // Custom implementation of `ScalarValue::from_displayable()` method -/// // for efficient conversions from `CompactString` into `MyScalarValue`. -/// fn from_compact_str(s: &Str) -> MyScalarValue { -/// use juniper::AnyExt as _; // allows downcasting directly on types without `dyn` -/// -/// if let Some(s) = s.downcast_ref::() { -/// MyScalarValue::String(s.clone()) -/// } else { -/// s.to_string().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) -/// } -/// } -/// } -/// /// impl<'de> Deserialize<'de> for MyScalarValue { /// fn deserialize>(de: D) -> Result { /// struct Visitor; @@ -165,7 +131,7 @@ pub trait ParseScalarValue { /// } /// /// fn visit_string(self, s: String) -> Result { -/// Ok(MyScalarValue::String(s.into())) +/// Ok(MyScalarValue::String(s)) /// } /// } /// @@ -711,6 +677,15 @@ impl<'a, S: ScalarValue> IntoFieldError for WrongInputScalarTypeError<'a, S> } } +/// Conversion of a Rust data type into a [`ScalarValue`]. +/// +/// # Implementation +/// +/// Implementing this trait for a type allows to specify this type directly in the `to_output()` +/// function when implementing a [`GraphQLScalar`] via [derive macro](macro@GraphQLScalar). +/// +/// Also, `#[derive(`[`GraphQLScalar`](macro@GraphQLScalar)`)]` automatically implements this trait +/// for a type. pub trait ToScalarValue { /// Converts this value into a [`ScalarValue`]. #[must_use] From b2cdc1440178e692a4a46d0a6470f653c1afa35f Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 19:02:06 +0300 Subject: [PATCH 09/14] Upd Book --- book/Cargo.toml | 2 + book/src/lib.rs | 1 + book/src/types/scalars.md | 90 +++++++++++++++++++++++++------------- juniper_codegen/Cargo.toml | 3 +- juniper_codegen/src/lib.rs | 85 ++++++++++++++++++++++------------- 5 files changed, 119 insertions(+), 62 deletions(-) diff --git a/book/Cargo.toml b/book/Cargo.toml index dafb62ac9..02f95cf5e 100644 --- a/book/Cargo.toml +++ b/book/Cargo.toml @@ -7,6 +7,8 @@ publish = false [dependencies] dataloader = "0.18" # for Book only +derive_more = { version = "2.0", features = ["display", "from", "try_into"] } +juniper = { path = "../juniper", features = ["jiff"] } [lints.clippy] allow_attributes = "warn" diff --git a/book/src/lib.rs b/book/src/lib.rs index 31005941f..0d864a4ee 100644 --- a/book/src/lib.rs +++ b/book/src/lib.rs @@ -1,3 +1,4 @@ //! Crate keeping dependencies for running Book tests. use dataloader as _; +use juniper as _; diff --git a/book/src/types/scalars.md b/book/src/types/scalars.md index 08cf0981e..16a4a62e6 100644 --- a/book/src/types/scalars.md +++ b/book/src/types/scalars.md @@ -85,15 +85,49 @@ pub struct UserId(String); In case we need to customize [resolving][7] of a [custom GraphQL scalar][2] value (change the way it gets executed), the `#[graphql(to_output_with = )]` attribute is the way to do so: ```rust # extern crate juniper; -# use juniper::{GraphQLScalar, IntoValue as _, ScalarValue, Value}; +# use juniper::GraphQLScalar; # #[derive(GraphQLScalar)] #[graphql(to_output_with = to_output, transparent)] struct Incremented(i32); -/// Increments [`Incremented`] before converting into a [`Value`]. -fn to_output(v: &Incremented) -> Value { - (v.0 + 1).into_value() +fn to_output(v: &Incremented) -> i32 { + // ^^^ any concrete type having `ToScalarValue` implementation + // could be used + v.0 + 1 +} +# +# fn main() {} +``` + +The provided function is polymorphic by its output type: +```rust +# extern crate jiff; +# extern crate juniper; +# use std::fmt::Display; +# use juniper::{GraphQLScalar, ScalarValue}; +# +#[derive(GraphQLScalar)] +#[graphql(to_output_with = Self::to_output, transparent)] +struct Incremented(i32); + +impl Incremented { + fn to_output(v: &Incremented) -> S { + // ^^^^^^^^^^^^^^ returning generic or concrete `ScalarValue` is also OK + (v.0 + 1).into() + } +} + +#[derive(GraphQLScalar)] +#[graphql(to_output_with = Self::to_output, transparent)] +struct CustomDateTime(jiff::Timestamp); + +impl CustomDateTime { + fn to_output(&self) -> impl Display { + // ^^^^^^^^^^^^ in this case macro expansion uses the + // `ScalarValue::from_displayable_non_static()` conversion + self.0.strftime("%Y-%m-%d %H:%M:%S%.fZ") + } } # # fn main() {} @@ -117,7 +151,7 @@ impl UserId { input: &str, // ^^^^ any concrete type having `FromScalarValue` implementation could be used ) -> Result> { - // ^^^^^^^^ must implement `IntoFieldError` + // ^^^^^^^^ must implement `IntoFieldError` input .strip_prefix("id: ") .ok_or_else(|| { @@ -130,7 +164,7 @@ impl UserId { # fn main() {} ``` -The provided function is polymorphic by input and output types: +The provided function is polymorphic by its input and output types: ```rust # extern crate juniper; # use juniper::{GraphQLScalar, Scalar, ScalarValue}; @@ -169,7 +203,7 @@ Customization of which tokens a [custom GraphQL scalar][0] type should be parsed ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, # }; # #[derive(GraphQLScalar)] @@ -186,10 +220,12 @@ enum StringOrInt { Int(i32), } -fn to_output(v: &StringOrInt) -> Value { +fn to_output(v: &StringOrInt) -> S { match v { - StringOrInt::String(s) => Value::scalar(s.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + StringOrInt::String(s) => S::from_displayable(s), + // ^^^^^^^^^^^^^^^^^^^ preferable conversion for types + // represented by string token + StringOrInt::Int(i) => (*i).into(), } } @@ -216,7 +252,7 @@ Instead of providing all custom functions separately, it's possible to provide a ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, # }; # #[derive(GraphQLScalar)] @@ -229,10 +265,10 @@ enum StringOrInt { mod string_or_int { use super::*; - pub(super) fn to_output(v: &StringOrInt) -> Value { + pub(super) fn to_output(v: &StringOrInt) -> S { match v { - StringOrInt::String(s) => Value::scalar(s.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + StringOrInt::String(s) => S::from_displayable(s), + StringOrInt::Int(i) => (*i).into(), } } @@ -256,7 +292,7 @@ A regular `impl` block is also suitable for that: ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value, +# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, # }; # #[derive(GraphQLScalar)] @@ -267,10 +303,10 @@ enum StringOrInt { } impl StringOrInt { - fn to_output(&self) -> Value { + fn to_output(&self) -> S { match self { - Self::String(s) => Value::scalar(s.to_owned()), - Self::Int(i) => Value::scalar(*i), + Self::String(s) => S::from_displayable(s), + Self::Int(i) => (*i).into(), } } @@ -297,7 +333,7 @@ At the same time, any custom function still may be specified separately, if requ ```rust # extern crate juniper; # use juniper::{ -# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, Value, +# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, # }; # #[derive(GraphQLScalar)] @@ -313,13 +349,10 @@ enum StringOrInt { mod string_or_int { use super::*; - pub(super) fn to_output(v: &StringOrInt) -> Value - where - S: ScalarValue, - { + pub(super) fn to_output(v: &StringOrInt) -> S { match v { - StringOrInt::String(s) => Value::scalar(s.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + StringOrInt::String(s) => S::from_displayable(s), + StringOrInt::Int(i) => (*i).into(), } } @@ -367,11 +400,12 @@ For implementing [custom scalars][2] on foreign types there is [`#[graphql_scala # } # # use juniper::DefaultScalarValue as CustomScalarValue; -use juniper::{ScalarValue, Value, graphql_scalar}; +use juniper::{ScalarValue, graphql_scalar}; #[graphql_scalar] #[graphql( with = date_scalar, + to_output_with = ScalarValue::from_displayable, // use `Display` representation parse_token(String), scalar = CustomScalarValue, )] @@ -381,10 +415,6 @@ type Date = date::Date; mod date_scalar { use super::*; - - pub(super) fn to_output(v: &Date) -> Value { - Value::scalar(v.to_string()) - } 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/Cargo.toml b/juniper_codegen/Cargo.toml index d1d241919..e569bd03b 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -31,7 +31,8 @@ url = "2.0" [dev-dependencies] derive_more = { version = "2.0", features = ["from", "try_into"] } futures = "0.3.22" -juniper = { path = "../juniper" } +jiff = { version = "0.2", features = ["std"], default-features = false } +juniper = { path = "../juniper", features = ["jiff"] } serde = "1.0.122" [lints.clippy] diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index da59dfdd9..05f780c1d 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -421,15 +421,45 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// Customization of a [GraphQL scalar][0] type resolving is possible via /// `#[graphql(to_output_with = )]` attribute: /// ```rust -/// # use juniper::{GraphQLScalar, IntoValue as _, ScalarValue, Value}; +/// # use juniper::GraphQLScalar; /// # /// #[derive(GraphQLScalar)] /// #[graphql(to_output_with = to_output, transparent)] /// struct Incremented(i32); /// -/// /// Increments [`Incremented`] before converting into a [`Value`]. -/// fn to_output(v: &Incremented) -> Value { -/// (v.0 + 1).into_value() +/// fn to_output(v: &Incremented) -> i32 { +/// // ^^^ any concrete type having `ToScalarValue` implementation +/// // could be used +/// v.0 + 1 +/// } +/// ``` +/// +/// The provided function is polymorphic by its output type: +/// ```rust +/// # use std::fmt::Display; +/// # use juniper::{GraphQLScalar, ScalarValue}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql(to_output_with = Self::to_output, transparent)] +/// struct Incremented(i32); +/// +/// impl Incremented { +/// fn to_output(v: &Incremented) -> S { +/// // ^^^^^^^^^^^^^^ returning generic or concrete `ScalarValue` is also OK +/// (v.0 + 1).into() +/// } +/// } +/// +/// #[derive(GraphQLScalar)] +/// #[graphql(to_output_with = Self::to_output, transparent)] +/// struct CustomDateTime(jiff::Timestamp); +/// +/// impl CustomDateTime { +/// fn to_output(&self) -> impl Display { +/// // ^^^^^^^^^^^^ in this case macro expansion uses the +/// // `ScalarValue::from_displayable_non_static()` conversion +/// self.0.strftime("%Y-%m-%d %H:%M:%S%.fZ") +/// } /// } /// ``` /// @@ -450,7 +480,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// input: &str, /// // ^^^^ any concrete type having `FromScalarValue` implementation could be used /// ) -> Result> { -/// // ^^^^^^^^ must implement `IntoFieldError` +/// // ^^^^^^^^ must implement `IntoFieldError` /// input /// .strip_prefix("id: ") /// .ok_or_else(|| { @@ -461,7 +491,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// The provided function is polymorphic by input and output types: +/// The provided function is polymorphic by its input and output types: /// ```rust /// # use juniper::{GraphQLScalar, Scalar, ScalarValue}; /// # @@ -497,7 +527,6 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// ```rust /// # use juniper::{ /// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, -/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -514,10 +543,12 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// Int(i32), /// } /// -/// fn to_output(v: &StringOrInt) -> Value { +/// fn to_output(v: &StringOrInt) -> S { /// match v { -/// StringOrInt::String(s) => Value::scalar(s.to_owned()), -/// StringOrInt::Int(i) => Value::scalar(*i), +/// StringOrInt::String(s) => S::from_displayable(s), +/// // ^^^^^^^^^^^^^^^^^^^ preferable conversion for types +/// // represented by string token +/// StringOrInt::Int(i) => (*i).into(), /// } /// } /// @@ -543,7 +574,6 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// ```rust /// # use juniper::{ /// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, -/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -556,10 +586,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// mod string_or_int { /// use super::*; /// -/// pub(super) fn to_output(v: &StringOrInt) -> Value { +/// pub(super) fn to_output(v: &StringOrInt) -> S { /// match v { -/// StringOrInt::String(s) => Value::scalar(s.to_owned()), -/// StringOrInt::Int(i) => Value::scalar(*i), +/// StringOrInt::String(s) => S::from_displayable(s), +/// StringOrInt::Int(i) => (*i).into(), /// } /// } /// @@ -583,7 +613,6 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// ```rust /// # use juniper::{ /// # GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, -/// # Value, /// # }; /// # /// #[derive(GraphQLScalar)] @@ -594,10 +623,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// } /// /// impl StringOrInt { -/// fn to_output(&self) -> Value { +/// fn to_output(&self) -> S { /// match self { -/// Self::String(s) => Value::scalar(s.to_owned()), -/// Self::Int(i) => Value::scalar(*i), +/// Self::String(s) => S::from_displayable(s), +/// Self::Int(i) => (*i).into(), /// } /// } /// @@ -622,7 +651,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, Scalar, ScalarToken, ScalarValue, Value}; +/// # use juniper::{GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue}; /// # /// #[derive(GraphQLScalar)] /// #[graphql( @@ -637,13 +666,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { /// mod string_or_int { /// use super::*; /// -/// pub(super) fn to_output(v: &StringOrInt) -> Value -/// where -/// S: ScalarValue, -/// { +/// pub(super) fn to_output(v: &StringOrInt) -> S { /// match v { -/// StringOrInt::String(s) => Value::scalar(s.to_owned()), -/// StringOrInt::Int(i) => Value::scalar(*i), +/// StringOrInt::String(s) => S::from_displayable(s), +/// StringOrInt::Int(i) => (*i).into(), /// } /// } /// @@ -735,11 +761,12 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// # } /// # /// # use juniper::DefaultScalarValue as CustomScalarValue; -/// use juniper::{graphql_scalar, ScalarValue, Value}; +/// use juniper::{graphql_scalar, ScalarValue}; /// /// #[graphql_scalar] /// #[graphql( /// with = date_scalar, +/// to_output_with = ScalarValue::from_displayable, // use `Display` representation /// parse_token(String), /// scalar = CustomScalarValue, /// )] @@ -748,11 +775,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// // ^^^^^^^^^^ type from another crate /// /// mod date_scalar { -/// use super::*; -/// -/// pub(super) fn to_output(v: &Date) -> Value { -/// Value::scalar(v.to_string()) -/// } +/// use super::Date; /// /// pub(super) fn from_input(s: &str) -> Result> { /// s.parse().map_err(|e| format!("Failed to parse `Date`: {e}").into()) From 20d28cb03690d62de1e576e618b90e6ea1ba6b30 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 19:52:50 +0300 Subject: [PATCH 10/14] Upd --- book/Cargo.toml | 1 + book/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/book/Cargo.toml b/book/Cargo.toml index 4b8390798..a41f14ece 100644 --- a/book/Cargo.toml +++ b/book/Cargo.toml @@ -9,6 +9,7 @@ publish = false anyhow = "1.0" dataloader = "0.18" derive_more = { version = "2.0", features = ["display", "from", "try_into"] } +jiff = { version = "0.2", features = ["std"], default-features = false } juniper = { path = "../juniper", features = ["anyhow", "jiff", "schema-language"] } juniper_subscriptions = { path = "../juniper_subscriptions" } serde_json = "1.0" diff --git a/book/src/lib.rs b/book/src/lib.rs index ebcb3e06a..4f0c0b421 100644 --- a/book/src/lib.rs +++ b/book/src/lib.rs @@ -3,6 +3,7 @@ use anyhow as _; use dataloader as _; use derive_more as _; +use jiff as _; use juniper as _; use juniper_subscriptions as _; use serde_json as _; From c7bfd1a1b63f886961be49f22f658a117f1153d3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Jun 2025 20:13:39 +0300 Subject: [PATCH 11/14] Fill up CHANGELOGs --- juniper/CHANGELOG.md | 11 ++++++++++- juniper_codegen/CHANGELOG.md | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index f513b5e83..940941585 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -93,6 +93,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - `From` and `Display` implementations are not derived anymore (recommended way is to use [`derive_more` crate] for this). - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: - Made provided `from_input()` function to accept `ScalarValue` (or anything `FromScalarValue`-convertible) directly instead of `InputValue`. ([#1327]) + - Made provided `to_output()` function to return `ScalarValue` directly instead of `Value`. ([#1330]) - Removed `LocalBoxFuture` usage from `http::tests::WsIntegration` trait. ([4b14c015]) ### Added @@ -112,18 +113,25 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324]) - `FromScalarValue` conversion trait. ([#1329]) - `TryToPrimitive` conversion trait aiding `ScalarValue` trait. ([#1327], [#1329]) +- `ToScalarValue` conversion trait. ([#1330]) - `ScalarValue` trait: - - `from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819]) + - `from_displayable()` and `from_displayable_non_static()` methods allowing to specialize `ScalarValue` conversion from/for custom string types. ([#1324], [#1330], [#819]) - `try_to::()` method defined by default as `FromScalarValue` alias. ([#1327], [#1329]) +- `#[derive(ScalarValue)]` macro: + - Support of top-level `#[value(from_displayable_with = ...)]` attribute. ([#1324]) + - Support of top-level `#[value(from_displayable_non_static_with = ...)]` attribute. ([#1330]) - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: - Support for specifying concrete types as input argument in provided `from_input()` function. ([#1327]) - Support for non-`Result` return type in provided `from_input()` function. ([#1327]) - `Scalar` transparent wrapper for aiding type inference in `from_input()` function when input argument is generic `ScalarValue`. ([#1327]) - Generating of `FromScalarValue` implementation. ([#1329]) + - Support for concrete and `impl Display` return types in provided `to_output()` function. ([#1330]) + - Generating of `ToScalarValue` implementation. ([#1330]) ### Changed - Upgraded [GraphiQL] to [5.0.0 version](https://github.com/graphql/graphiql/blob/graphiql%405.0.0/packages/graphiql/CHANGELOG.md#500). ([#1331]) +- Lifted `Sized` requirement from `ToInputValue` conversion trait. ([#1330]) ### Fixed @@ -147,6 +155,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1324]: /../../pull/1324 [#1327]: /../../pull/1327 [#1329]: /../../pull/1329 +[#1330]: /../../pull/1330 [#1331]: /../../pull/1331 [1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295 [20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3 diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md index 2ae84cba0..99a9be524 100644 --- a/juniper_codegen/CHANGELOG.md +++ b/juniper_codegen/CHANGELOG.md @@ -21,20 +21,25 @@ 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: - Made provided `from_input()` function to accept `ScalarValue` directly instead of `InputValue`. ([#1327]) + - Made provided `to_output()` function to return `ScalarValue` directly instead of `Value`. ([#1330]) ### Added - `#[derive(ScalarValue)]` macro: - Support of top-level `#[value(from_displayable_with = ...)]` attribute. ([#1324]) + - Support of top-level `#[value(from_displayable_non_static_with = ...)]` attribute. ([#1330]) - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: - Support for specifying concrete types as input argument in provided `from_input()` function. ([#1327]) - Support for non-`Result` return type in provided `from_input()` function. ([#1327]) - - Generating of `FromScalarValue` implementation. ([#1329]) + - Generating of `FromScalarValue` implementation. ([#1329]) + - Support for concrete and `impl Display` return types in provided `to_output()` function. ([#1330]) + - Generating of `ToScalarValue` implementation. ([#1330]) [#1272]: /../../pull/1272 [#1324]: /../../pull/1324 [#1327]: /../../pull/1327 [#1329]: /../../pull/1329 +[#1330]: /../../pull/1330 [1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295 From ddb61fdbe1ce2501e41b1b4d45f11f9a64cd5de2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 30 Jun 2025 11:40:35 +0300 Subject: [PATCH 12/14] Final polishing --- juniper/src/macros/helper/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index a44065b75..98fcc4597 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -62,6 +62,12 @@ 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. /// +/// # Priority +/// +/// 1. Functions returning [`Result`] are propagated "as is". +/// +/// 2. Any other function's output is wrapped into [`Result`] with an [`Infallible`] [`Err`]. +/// /// [0]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html pub trait ToResultCall { /// Input of this function. @@ -98,6 +104,18 @@ impl ToResultCall for fn(I) -> O { /// [Autoref-based specialized][0] coercion into a [`ScalarValue`] for a function call for providing /// a return-type polymorphism in macros. /// +/// # Priority +/// +/// 1. Functions returning a [`ScalarValue`] are propagated "as is". +/// +/// 2. Functions returning a [`String`] are followed by [`From`] conversion. +/// +/// 3. Functions returning anything implementing [`ToScalarValue`] conversion are followed by this +/// conversion. +/// +/// 4. Functions returning anything implementing [`Display`] are followed by the +/// [`ScalarValue::from_displayable_non_static()`] method call. +/// /// [0]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html pub trait ToScalarValueCall { /// Input of this function. From c372606f18fc222ce92bec234f163bb20b88929e Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 30 Jun 2025 12:03:49 +0300 Subject: [PATCH 13/14] Fix --- .../input-object/derive_incompatible_field_type.stderr | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr index 1a6c2f371..7cf1a9e2f 100644 --- a/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr @@ -14,7 +14,6 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied `TypeKind` implements `IsInputType<__S>` `Vec` implements `IsInputType` and $N others - error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:10:12 | @@ -42,7 +41,6 @@ note: required by a bound in `Registry::::arg` | where | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::::arg` - error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:8:10 | @@ -60,7 +58,6 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied `[T; N]` implements `FromInputValue` and $N others = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `ObjectA: ToInputValue<_>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:8:10 | @@ -69,12 +66,12 @@ error[E0277]: the trait bound `ObjectA: ToInputValue<_>` is not satisfied | = help: the following other types implement trait `ToInputValue`: `&T` implements `ToInputValue` - `&[T]` implements `ToInputValue` - `&str` implements `ToInputValue` `Arc` implements `ToInputValue` `ArcStr` implements `ToInputValue<__S>` `Box` implements `ToInputValue` `ID` implements `ToInputValue<__S>` `Object` implements `ToInputValue<__S>` + `TypeKind` implements `ToInputValue<__S>` + `Value` implements `ToInputValue` and $N others = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) From f24d06b92c0e647fba266c55ad3e944af86d636f Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 30 Jun 2025 12:32:07 +0300 Subject: [PATCH 14/14] Fix --- .../fail/input-object/derive_incompatible_field_type.stderr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr index 7cf1a9e2f..5aee56316 100644 --- a/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr @@ -14,6 +14,7 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied `TypeKind` implements `IsInputType<__S>` `Vec` implements `IsInputType` and $N others + error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:10:12 | @@ -41,6 +42,7 @@ note: required by a bound in `Registry::::arg` | where | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::::arg` + error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:8:10 | @@ -58,6 +60,7 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied `[T; N]` implements `FromInputValue` and $N others = note: this error originates in the derive macro `GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `ObjectA: ToInputValue<_>` is not satisfied --> fail/input-object/derive_incompatible_field_type.rs:8:10 |