diff --git a/book/Cargo.toml b/book/Cargo.toml index 6c1283d49..a41f14ece 100644 --- a/book/Cargo.toml +++ b/book/Cargo.toml @@ -9,7 +9,8 @@ publish = false anyhow = "1.0" dataloader = "0.18" derive_more = { version = "2.0", features = ["display", "from", "try_into"] } -juniper = { path = "../juniper", features = ["anyhow", "schema-language"] } +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" tokio = { version = "1.0", features = ["sync"] } 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 _; 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/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/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/ast.rs b/juniper/src/ast.rs index ebf329cce..cb68ee4db 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -231,8 +231,8 @@ pub trait FromInputValue: Sized { } } -/// Losslessly clones a Rust data type into an InputValue. -pub trait ToInputValue: Sized { +/// Losslessly clones a Rust data type into an [`InputValue`]. +pub trait ToInputValue { /// Performs the conversion. fn to_input_value(&self) -> InputValue; } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 387436ad0..060487793 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,5 +1,5 @@ use crate::{ - GraphQLInputObject, GraphQLScalar, ScalarValue, Value, + GraphQLInputObject, GraphQLScalar, executor::Variables, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, @@ -14,8 +14,8 @@ use crate::{ struct TestComplexScalar; impl TestComplexScalar { - fn to_output(&self) -> Value { - graphql_value!("SerializedValue") + fn to_output(&self) -> &'static str { + "SerializedValue" } fn from_input(s: &str) -> Result> { diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs index 1defe431b..27e1ea015 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::{ScalarValue, graphql_scalar}; // TODO: Try remove on upgrade of `bigdecimal` crate. mod for_minimal_versions_check_only { @@ -29,19 +27,18 @@ 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, + to_output_with = ScalarValue::from_displayable, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/bigdecimal", )] type BigDecimal = bigdecimal::BigDecimal; mod bigdecimal_scalar { - use super::*; - - pub(super) fn to_output(v: &BigDecimal) -> Value { - Value::scalar(v.to_string()) - } + use super::BigDecimal; + use crate::{Scalar, ScalarValue}; pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { @@ -50,13 +47,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 +64,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 +87,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 +126,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..4e27b41f1 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,17 @@ use crate::{ScalarValue, Value, graphql_scalar}; pub type LocalDate = chrono::NaiveDate; mod local_date { - use super::*; + use std::fmt::Display; + + 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) -> impl Display { + v.format(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -75,7 +75,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", @@ -83,9 +84,11 @@ mod local_date { pub type LocalTime = chrono::NaiveTime; mod local_time { + use std::fmt::Display; + use chrono::Timelike as _; - use super::*; + use super::LocalTime; /// Full format of a [`LocalTime` scalar][1]. /// @@ -102,18 +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) -> 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) -> impl Display { + if v.nanosecond() == 0 { + v.format(FORMAT_NO_MILLIS) + } else { + v.format(FORMAT) + } } pub(super) fn from_input(s: &str) -> Result> { @@ -134,7 +131,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 +140,17 @@ mod local_time { pub type LocalDateTime = chrono::NaiveDateTime; mod local_date_time { - use super::*; + use std::fmt::Display; + + 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) -> impl Display { + v.format(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -174,7 +171,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 +184,19 @@ mod local_date_time { pub type DateTime = chrono::DateTime; mod date_time { - use chrono::{SecondsFormat, Utc}; + use std::fmt::Display; - use super::*; + use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc}; - pub(super) fn to_output(v: &DateTime) -> Value + use super::{DateTime, FromFixedOffset}; + + pub(super) fn to_output(v: &DateTime) -> String where - S: ScalarValue, - Tz: chrono::TimeZone, - Tz::Offset: fmt::Display, + Tz: TimeZone, + Tz::Offset: Display, { - Value::scalar( - v.with_timezone(&Utc) - .to_rfc3339_opts(SecondsFormat::AutoSi, true), - ) + v.with_timezone(&Utc) + .to_rfc3339_opts(SecondsFormat::AutoSi, true) } 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..f434b9404 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::{ScalarValue, 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) -> impl Display { + v.strftime(FORMAT) } 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,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) -> 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) -> impl Display { + if v.subsec_nanosecond() == 0 { + v.strftime(FORMAT_NO_MILLIS) + } else { + v.strftime(FORMAT) + } } pub(super) fn from_input(s: &str) -> Result> { @@ -170,7 +163,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 +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) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + pub(super) fn to_output(v: &LocalDateTime) -> impl Display { + v.strftime(FORMAT) } pub(super) fn from_input(s: &str) -> Result> { @@ -208,7 +199,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", @@ -216,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]. @@ -225,15 +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) -> Value - where - S: ScalarValue, - { - Value::scalar(v.strftime(FORMAT).to_string()) + 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()) } } @@ -253,27 +241,21 @@ 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, + 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) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) - } + 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()) } } @@ -288,27 +270,21 @@ 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, + 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) -> Value - where - S: ScalarValue, - { - Value::scalar(v.to_string()) - } + 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()) } } @@ -326,7 +302,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 +315,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 +327,7 @@ mod time_zone_or_utc_offset { .to_string() }, ToOwned::to_owned, - )) + ) } pub(super) fn from_input(s: &str) -> Result> { @@ -392,8 +366,10 @@ 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, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone", )] @@ -423,14 +399,7 @@ 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()) - } + use super::TimeZone; pub(super) fn from_input(s: &str) -> Result> { s.parse() @@ -446,7 +415,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 +439,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 } 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..f333962a8 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::{ScalarValue, graphql_scalar}; /// 128 bit representation of a fixed-precision decimal number. /// @@ -26,19 +24,18 @@ 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, + to_output_with = ScalarValue::from_displayable, parse_token(i32, f64, String), specified_by_url = "https://docs.rs/rust_decimal", )] type Decimal = rust_decimal::Decimal; mod rust_decimal_scalar { - use super::*; - - pub(super) fn to_output(v: &Decimal) -> Value { - Value::scalar(v.to_string()) - } + use super::Decimal; + use crate::{Scalar, ScalarValue}; pub(super) fn from_input(v: &Scalar) -> Result> { if let Some(i) = v.try_to_int() { @@ -50,7 +47,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 +56,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 +73,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 +107,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..9a7f889a6 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,9 @@ 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 { + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -79,7 +78,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 +106,13 @@ 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 { + 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 +133,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 +150,9 @@ 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 { + v.format(FORMAT) + .unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}")) } pub(super) fn from_input(s: &str) -> Result> { @@ -175,7 +172,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 +183,10 @@ 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 { + 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 +211,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 +222,9 @@ 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 { + 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..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::{ScalarValue, Value, graphql_scalar}; +use crate::{ScalarValue, graphql_scalar}; /// [Universally Unique Identifier][0] (UUID). /// @@ -20,20 +20,18 @@ 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, + to_output_with = ScalarValue::from_displayable, parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/uuid", )] type Uuid = uuid::Uuid; mod uuid_scalar { - use super::*; - - pub(super) fn to_output(v: &Uuid) -> Value { - Value::scalar(v.to_string()) - } + use super::Uuid; 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/lib.rs b/juniper/src/lib.rs index 7741d297f..373ee877b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -104,7 +104,8 @@ pub use crate::{ validation::RuleError, value::{ AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult, - ParseScalarValue, Scalar, ScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, + ParseScalarValue, Scalar, ScalarValue, ToScalarValue, TryToPrimitive, Value, + WrongInputScalarTypeError, }, }; diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 75a414aa2..98fcc4597 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`]. /// @@ -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. @@ -75,6 +81,16 @@ pub trait ToResultCall { fn __to_result_call(&self, input: Self::Input) -> Result; } +impl ToResultCall for &fn(I) -> Result { + type Input = I; + type Output = O; + type Error = E; + + fn __to_result_call(&self, input: Self::Input) -> Result { + self(input) + } +} + impl ToResultCall for fn(I) -> O { type Input = I; type Output = O; @@ -85,12 +101,72 @@ impl ToResultCall for fn(I) -> O { } } -impl ToResultCall for &fn(I) -> Result { +/// [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. + 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 +where + S: ScalarValue, +{ type Input = I; - type Output = O; - type Error = E; - fn __to_result_call(&self, input: Self::Input) -> Result { + fn __to_scalar_value_call(&self, input: Self::Input) -> S { self(input) } } + +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; + + fn __to_scalar_value_call(&self, input: Self::Input) -> S { + 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_displayable_non_static(&self(input)) + } +} diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 4ea31b990..58047dd8f 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -90,7 +90,10 @@ impl> FromInputValue for Option { } } -impl> ToInputValue for Option { +impl ToInputValue for Option +where + T: ToInputValue, +{ fn to_input_value(&self) -> InputValue { match self { Some(v) => v.to_input_value(), @@ -176,7 +179,6 @@ impl> FromInputValue for Vec { impl ToInputValue for Vec where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -270,10 +272,9 @@ where } } -impl ToInputValue for &[T] +impl ToInputValue for [T] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) @@ -462,7 +463,6 @@ where impl ToInputValue for [T; N] where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { InputValue::list(self.iter().map(T::to_input_value).collect()) diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 3c4b47895..79d399f68 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -302,11 +302,10 @@ impl> FromInputValue for Nullable { impl ToInputValue for Nullable where T: ToInputValue, - S: ScalarValue, { fn to_input_value(&self) -> InputValue { - match *self { - Self::Some(ref v) => v.to_input_value(), + match self { + Self::Some(v) => v.to_input_value(), _ => InputValue::null(), } } diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 1bf66f5a0..a3d496f7a 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -1,4 +1,4 @@ -use std::{fmt, sync::Arc}; +use std::sync::Arc; use arcstr::ArcStr; @@ -11,7 +11,7 @@ use crate::{ async_await::GraphQLValueAsync, base::{Arguments, GraphQLType, GraphQLValue}, }, - value::{FromScalarValue, ScalarValue}, + value::{FromScalarValue, ScalarValue, ToScalarValue}, }; impl GraphQLType for Box @@ -99,6 +99,15 @@ where } } +impl ToScalarValue for Box +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Box where S: ScalarValue, @@ -113,8 +122,7 @@ where impl ToInputValue for Box where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -204,10 +212,18 @@ where } } +impl ToScalarValue for &T +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl ToInputValue for &T where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() @@ -299,6 +315,15 @@ where } } +impl ToScalarValue for Arc +where + T: ToScalarValue + ?Sized, +{ + fn to_scalar_value(&self) -> S { + (**self).to_scalar_value() + } +} + impl FromInputValue for Arc where S: ScalarValue, @@ -313,8 +338,7 @@ where impl ToInputValue for Arc where - S: fmt::Debug, - T: ToInputValue, + T: ToInputValue + ?Sized, { fn to_input_value(&self) -> InputValue { (**self).to_input_value() diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 272fca57c..aaf72df7b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -17,7 +17,7 @@ use crate::{ subscriptions::GraphQLSubscriptionValue, }, value::{ - FromScalarValue, ParseScalarResult, ScalarValue, TryToPrimitive, Value, + FromScalarValue, ParseScalarResult, ScalarValue, ToScalarValue, TryToPrimitive, Value, WrongInputScalarTypeError, }, }; @@ -35,8 +35,8 @@ use crate::{ pub struct ID(Box); impl ID { - fn to_output(&self) -> Value { - Value::scalar(self.0.clone().into_string()) + fn to_output(&self) -> &str { + &self.0 } fn from_input(v: &Scalar) -> Result> { @@ -59,7 +59,11 @@ impl ID { } #[graphql_scalar] -#[graphql(with = impl_string_scalar, from_input_with = __builtin)] +#[graphql( + with = impl_string_scalar, + to_output_with = String::as_str + from_input_with = __builtin, +)] type String = std::string::String; mod impl_string_scalar { @@ -76,10 +80,6 @@ mod impl_string_scalar { } } - pub(super) fn to_output(v: &str) -> Value { - Value::scalar(v.to_owned()) - } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); @@ -185,16 +185,17 @@ 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, IntoValue as _, Scalar, ScalarValue, Value}; - - pub(super) fn to_output(v: &ArcStr) -> Value { - v.into_value() - } + use crate::{FromScalarValue, Scalar, ScalarValue}; pub(super) fn from_input( v: &Scalar, @@ -208,16 +209,17 @@ 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, IntoValue as _, Scalar, ScalarValue, Value}; - - pub(super) fn to_output(v: &CompactString) -> Value { - v.into_value() - } + use crate::{FromScalarValue, Scalar, ScalarValue}; pub(super) fn from_input( v: &Scalar, @@ -291,15 +293,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 +304,21 @@ where } } +impl ToScalarValue for str { + fn to_scalar_value(&self) -> S { + S::from_displayable(self) + } +} + +impl ToInputValue for str +where + Self: ToScalarValue, +{ + fn to_input_value(&self) -> InputValue { + InputValue::Scalar(self.to_scalar_value()) + } +} + #[graphql_scalar] #[graphql(with = impl_boolean_scalar, from_input_with = __builtin)] type Boolean = bool; @@ -329,8 +337,8 @@ mod impl_boolean_scalar { } } - pub(super) fn to_output(v: &Boolean) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Boolean) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -357,8 +365,8 @@ mod impl_int_scalar { } } - pub(super) fn to_output(v: &Int) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Int) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { @@ -390,8 +398,8 @@ mod impl_float_scalar { } } - pub(super) fn to_output(v: &Float) -> Value { - Value::scalar(*v) + pub(super) fn to_output(v: &Float) -> S { + (*v).into() } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult { diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e6deed359..ec2b08f80 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -10,7 +10,7 @@ pub use self::{ object::Object, scalar::{ AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar, - ScalarValue, TryToPrimitive, WrongInputScalarTypeError, + ScalarValue, ToScalarValue, TryToPrimitive, WrongInputScalarTypeError, }, }; use crate::{ diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index e0cd173aa..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)) /// } /// } /// @@ -476,17 +442,158 @@ pub trait ScalarValue: /// Creates this [`ScalarValue`] from the provided [`Display`]able 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`]. + /// /// This method should be implemented if [`ScalarValue`] implementation uses some custom string /// type inside to enable efficient conversion from values of this type. /// + /// ```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(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`]. /// - /// # Example + /// 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), + /// } /// - /// See the [example in trait documentation](ScalarValue#example) for how it can be used. + /// // 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_displayable(s: &Str) -> Self { - s.to_string().into() + fn from_displayable_non_static(value: &T) -> Self { + value.to_string().into() } } @@ -570,6 +677,21 @@ 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] + fn to_scalar_value(&self) -> S; +} + /// Transparent wrapper over a value, indicating it being a [`ScalarValue`]. /// /// Used in [`GraphQLScalar`] definitions to distinguish a concrete type for a generic diff --git a/juniper_codegen/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 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/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index f81c25537..7d9c463e7 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, Context = (), TypeInfo = ()> + }); + + 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 } } } @@ -631,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 } } } @@ -817,78 +937,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 - 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/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()) 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 } } } 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..5aee56316 100644 --- a/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_field_type.stderr @@ -69,12 +69,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) 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> {