Skip to content

Commit ffe8064

Browse files
authored
Strip Value from to_output() function for GraphQL scalars (#1330)
- add `ToScalarValue` conversion trait - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: - require `to_output()` function to return `ScalarValue` directly instead of `Value` - support concrete and `impl Display` return types in provided `to_output()` function - generate `ToScalarValue` implementation - add non-required `from_displayable_non_static()` method to `ScalarValue` trait - support top-level `#[value(from_displayable_non_static_with = ...)]` attribute in `#[derive(ScalarValue)]` macro
1 parent c4e5ca8 commit ffe8064

36 files changed

+906
-586
lines changed

book/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ publish = false
99
anyhow = "1.0"
1010
dataloader = "0.18"
1111
derive_more = { version = "2.0", features = ["display", "from", "try_into"] }
12-
juniper = { path = "../juniper", features = ["anyhow", "schema-language"] }
12+
jiff = { version = "0.2", features = ["std"], default-features = false }
13+
juniper = { path = "../juniper", features = ["anyhow", "jiff", "schema-language"] }
1314
juniper_subscriptions = { path = "../juniper_subscriptions" }
1415
serde_json = "1.0"
1516
tokio = { version = "1.0", features = ["sync"] }

book/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use anyhow as _;
44
use dataloader as _;
55
use derive_more as _;
6+
use jiff as _;
67
use juniper as _;
78
use juniper_subscriptions as _;
89
use serde_json as _;

book/src/types/scalars.md

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,49 @@ pub struct UserId(String);
8585
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 = <fn path>)]` attribute is the way to do so:
8686
```rust
8787
# extern crate juniper;
88-
# use juniper::{GraphQLScalar, IntoValue as _, ScalarValue, Value};
88+
# use juniper::GraphQLScalar;
8989
#
9090
#[derive(GraphQLScalar)]
9191
#[graphql(to_output_with = to_output, transparent)]
9292
struct Incremented(i32);
9393

94-
/// Increments [`Incremented`] before converting into a [`Value`].
95-
fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
96-
(v.0 + 1).into_value()
94+
fn to_output(v: &Incremented) -> i32 {
95+
// ^^^ any concrete type having `ToScalarValue` implementation
96+
// could be used
97+
v.0 + 1
98+
}
99+
#
100+
# fn main() {}
101+
```
102+
103+
The provided function is polymorphic by its output type:
104+
```rust
105+
# extern crate jiff;
106+
# extern crate juniper;
107+
# use std::fmt::Display;
108+
# use juniper::{GraphQLScalar, ScalarValue};
109+
#
110+
#[derive(GraphQLScalar)]
111+
#[graphql(to_output_with = Self::to_output, transparent)]
112+
struct Incremented(i32);
113+
114+
impl Incremented {
115+
fn to_output<S: ScalarValue>(v: &Incremented) -> S {
116+
// ^^^^^^^^^^^^^^ returning generic or concrete `ScalarValue` is also OK
117+
(v.0 + 1).into()
118+
}
119+
}
120+
121+
#[derive(GraphQLScalar)]
122+
#[graphql(to_output_with = Self::to_output, transparent)]
123+
struct CustomDateTime(jiff::Timestamp);
124+
125+
impl CustomDateTime {
126+
fn to_output(&self) -> impl Display {
127+
// ^^^^^^^^^^^^ in this case macro expansion uses the
128+
// `ScalarValue::from_displayable_non_static()` conversion
129+
self.0.strftime("%Y-%m-%d %H:%M:%S%.fZ")
130+
}
97131
}
98132
#
99133
# fn main() {}
@@ -117,7 +151,7 @@ impl UserId {
117151
input: &str,
118152
// ^^^^ any concrete type having `FromScalarValue` implementation could be used
119153
) -> Result<Self, Box<str>> {
120-
// ^^^^^^^^ must implement `IntoFieldError`
154+
// ^^^^^^^^ must implement `IntoFieldError`
121155
input
122156
.strip_prefix("id: ")
123157
.ok_or_else(|| {
@@ -130,7 +164,7 @@ impl UserId {
130164
# fn main() {}
131165
```
132166

133-
The provided function is polymorphic by input and output types:
167+
The provided function is polymorphic by its input and output types:
134168
```rust
135169
# extern crate juniper;
136170
# use juniper::{GraphQLScalar, Scalar, ScalarValue};
@@ -169,7 +203,7 @@ Customization of which tokens a [custom GraphQL scalar][0] type should be parsed
169203
```rust
170204
# extern crate juniper;
171205
# use juniper::{
172-
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
206+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue,
173207
# };
174208
#
175209
#[derive(GraphQLScalar)]
@@ -186,10 +220,12 @@ enum StringOrInt {
186220
Int(i32),
187221
}
188222

189-
fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
223+
fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
190224
match v {
191-
StringOrInt::String(s) => Value::scalar(s.to_owned()),
192-
StringOrInt::Int(i) => Value::scalar(*i),
225+
StringOrInt::String(s) => S::from_displayable(s),
226+
// ^^^^^^^^^^^^^^^^^^^ preferable conversion for types
227+
// represented by string token
228+
StringOrInt::Int(i) => (*i).into(),
193229
}
194230
}
195231

@@ -216,7 +252,7 @@ Instead of providing all custom functions separately, it's possible to provide a
216252
```rust
217253
# extern crate juniper;
218254
# use juniper::{
219-
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
255+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue,
220256
# };
221257
#
222258
#[derive(GraphQLScalar)]
@@ -229,10 +265,10 @@ enum StringOrInt {
229265
mod string_or_int {
230266
use super::*;
231267

232-
pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
268+
pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
233269
match v {
234-
StringOrInt::String(s) => Value::scalar(s.to_owned()),
235-
StringOrInt::Int(i) => Value::scalar(*i),
270+
StringOrInt::String(s) => S::from_displayable(s),
271+
StringOrInt::Int(i) => (*i).into(),
236272
}
237273
}
238274

@@ -256,7 +292,7 @@ A regular `impl` block is also suitable for that:
256292
```rust
257293
# extern crate juniper;
258294
# use juniper::{
259-
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
295+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue,
260296
# };
261297
#
262298
#[derive(GraphQLScalar)]
@@ -267,10 +303,10 @@ enum StringOrInt {
267303
}
268304

269305
impl StringOrInt {
270-
fn to_output<S: ScalarValue>(&self) -> Value<S> {
306+
fn to_output<S: ScalarValue>(&self) -> S {
271307
match self {
272-
Self::String(s) => Value::scalar(s.to_owned()),
273-
Self::Int(i) => Value::scalar(*i),
308+
Self::String(s) => S::from_displayable(s),
309+
Self::Int(i) => (*i).into(),
274310
}
275311
}
276312

@@ -297,7 +333,7 @@ At the same time, any custom function still may be specified separately, if requ
297333
```rust
298334
# extern crate juniper;
299335
# use juniper::{
300-
# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, Value,
336+
# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue,
301337
# };
302338
#
303339
#[derive(GraphQLScalar)]
@@ -313,13 +349,10 @@ enum StringOrInt {
313349
mod string_or_int {
314350
use super::*;
315351

316-
pub(super) fn to_output<S>(v: &StringOrInt) -> Value<S>
317-
where
318-
S: ScalarValue,
319-
{
352+
pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
320353
match v {
321-
StringOrInt::String(s) => Value::scalar(s.to_owned()),
322-
StringOrInt::Int(i) => Value::scalar(*i),
354+
StringOrInt::String(s) => S::from_displayable(s),
355+
StringOrInt::Int(i) => (*i).into(),
323356
}
324357
}
325358

@@ -367,11 +400,12 @@ For implementing [custom scalars][2] on foreign types there is [`#[graphql_scala
367400
# }
368401
#
369402
# use juniper::DefaultScalarValue as CustomScalarValue;
370-
use juniper::{ScalarValue, Value, graphql_scalar};
403+
use juniper::{ScalarValue, graphql_scalar};
371404

372405
#[graphql_scalar]
373406
#[graphql(
374407
with = date_scalar,
408+
to_output_with = ScalarValue::from_displayable, // use `Display` representation
375409
parse_token(String),
376410
scalar = CustomScalarValue,
377411
)]
@@ -381,10 +415,6 @@ type Date = date::Date;
381415

382416
mod date_scalar {
383417
use super::*;
384-
385-
pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
386-
Value::scalar(v.to_string())
387-
}
388418

389419
pub(super) fn from_input(s: &str) -> Result<Date, Box<str>> {
390420
s.parse().map_err(|e| format!("Failed to parse `Date`: {e}").into())

juniper/CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
9393
- `From` and `Display` implementations are not derived anymore (recommended way is to use [`derive_more` crate] for this).
9494
- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros:
9595
- Made provided `from_input()` function to accept `ScalarValue` (or anything `FromScalarValue`-convertible) directly instead of `InputValue`. ([#1327])
96+
- Made provided `to_output()` function to return `ScalarValue` directly instead of `Value`. ([#1330])
9697
- Removed `LocalBoxFuture` usage from `http::tests::WsIntegration` trait. ([4b14c015])
9798

9899
### Added
@@ -112,18 +113,25 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
112113
- `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324])
113114
- `FromScalarValue` conversion trait. ([#1329])
114115
- `TryToPrimitive` conversion trait aiding `ScalarValue` trait. ([#1327], [#1329])
116+
- `ToScalarValue` conversion trait. ([#1330])
115117
- `ScalarValue` trait:
116-
- `from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819])
118+
- `from_displayable()` and `from_displayable_non_static()` methods allowing to specialize `ScalarValue` conversion from/for custom string types. ([#1324], [#1330], [#819])
117119
- `try_to::<T>()` method defined by default as `FromScalarValue<T>` alias. ([#1327], [#1329])
120+
- `#[derive(ScalarValue)]` macro:
121+
- Support of top-level `#[value(from_displayable_with = ...)]` attribute. ([#1324])
122+
- Support of top-level `#[value(from_displayable_non_static_with = ...)]` attribute. ([#1330])
118123
- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros:
119124
- Support for specifying concrete types as input argument in provided `from_input()` function. ([#1327])
120125
- Support for non-`Result` return type in provided `from_input()` function. ([#1327])
121126
- `Scalar` transparent wrapper for aiding type inference in `from_input()` function when input argument is generic `ScalarValue`. ([#1327])
122127
- Generating of `FromScalarValue` implementation. ([#1329])
128+
- Support for concrete and `impl Display` return types in provided `to_output()` function. ([#1330])
129+
- Generating of `ToScalarValue` implementation. ([#1330])
123130

124131
### Changed
125132

126133
- Upgraded [GraphiQL] to [5.0.0 version](https://github.com/graphql/graphiql/blob/graphiql%405.0.0/packages/graphiql/CHANGELOG.md#500). ([#1331])
134+
- Lifted `Sized` requirement from `ToInputValue` conversion trait. ([#1330])
127135

128136
### Fixed
129137

@@ -147,6 +155,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
147155
[#1324]: /../../pull/1324
148156
[#1327]: /../../pull/1327
149157
[#1329]: /../../pull/1329
158+
[#1330]: /../../pull/1330
150159
[#1331]: /../../pull/1331
151160
[1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295
152161
[20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3

juniper/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ tap = { version = "1.0.1", optional = true }
8080
void = { version = "1.0.2", optional = true }
8181

8282
[dev-dependencies]
83+
arcstr = { version = "1.1", features = ["serde"] }
8384
bencher = "0.1.2"
8485
chrono = { version = "0.4.30", features = ["alloc"], default-features = false }
8586
compact_str = { version = "0.9", features = ["serde"] }

juniper/src/ast.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ pub trait FromInputValue<S = DefaultScalarValue>: Sized {
231231
}
232232
}
233233

234-
/// Losslessly clones a Rust data type into an InputValue.
235-
pub trait ToInputValue<S = DefaultScalarValue>: Sized {
234+
/// Losslessly clones a Rust data type into an [`InputValue`].
235+
pub trait ToInputValue<S = DefaultScalarValue> {
236236
/// Performs the conversion.
237237
fn to_input_value(&self) -> InputValue<S>;
238238
}

juniper/src/executor_tests/variables.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
GraphQLInputObject, GraphQLScalar, ScalarValue, Value,
2+
GraphQLInputObject, GraphQLScalar,
33
executor::Variables,
44
graphql_object, graphql_value, graphql_vars,
55
parser::SourcePosition,
@@ -14,8 +14,8 @@ use crate::{
1414
struct TestComplexScalar;
1515

1616
impl TestComplexScalar {
17-
fn to_output<S: ScalarValue>(&self) -> Value<S> {
18-
graphql_value!("SerializedValue")
17+
fn to_output(&self) -> &'static str {
18+
"SerializedValue"
1919
}
2020

2121
fn from_input(s: &str) -> Result<Self, Box<str>> {

juniper/src/integrations/bigdecimal.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
//!
99
//! [`BigDecimal`]: bigdecimal::BigDecimal
1010
11-
use std::str::FromStr as _;
12-
13-
use crate::{Scalar, ScalarValue, Value, graphql_scalar};
11+
use crate::{ScalarValue, graphql_scalar};
1412

1513
// TODO: Try remove on upgrade of `bigdecimal` crate.
1614
mod for_minimal_versions_check_only {
@@ -29,19 +27,18 @@ mod for_minimal_versions_check_only {
2927
/// See also [`bigdecimal`] crate for details.
3028
///
3129
/// [`bigdecimal`]: https://docs.rs/bigdecimal
32-
#[graphql_scalar(
30+
#[graphql_scalar]
31+
#[graphql(
3332
with = bigdecimal_scalar,
33+
to_output_with = ScalarValue::from_displayable,
3434
parse_token(i32, f64, String),
3535
specified_by_url = "https://docs.rs/bigdecimal",
3636
)]
3737
type BigDecimal = bigdecimal::BigDecimal;
3838

3939
mod bigdecimal_scalar {
40-
use super::*;
41-
42-
pub(super) fn to_output<S: ScalarValue>(v: &BigDecimal) -> Value<S> {
43-
Value::scalar(v.to_string())
44-
}
40+
use super::BigDecimal;
41+
use crate::{Scalar, ScalarValue};
4542

4643
pub(super) fn from_input(v: &Scalar<impl ScalarValue>) -> Result<BigDecimal, Box<str>> {
4744
if let Some(i) = v.try_to_int() {
@@ -50,13 +47,14 @@ mod bigdecimal_scalar {
5047
// See akubera/bigdecimal-rs#103 for details:
5148
// https://github.com/akubera/bigdecimal-rs/issues/103
5249
let mut buf = ryu::Buffer::new();
53-
BigDecimal::from_str(buf.format(f))
50+
buf.format(f)
51+
.parse::<BigDecimal>()
5452
.map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {e}").into())
5553
} else {
5654
v.try_to::<&str>()
5755
.map_err(|e| e.to_string().into())
5856
.and_then(|s| {
59-
BigDecimal::from_str(s).map_err(|e| {
57+
s.parse::<BigDecimal>().map_err(|e| {
6058
format!("Failed to parse `BigDecimal` from `String`: {e}").into()
6159
})
6260
})
@@ -66,8 +64,6 @@ mod bigdecimal_scalar {
6664

6765
#[cfg(test)]
6866
mod test {
69-
use std::str::FromStr as _;
70-
7167
use crate::{FromInputValue as _, InputValue, ToInputValue as _, graphql_input_value};
7268

7369
use super::BigDecimal;
@@ -91,7 +87,7 @@ mod test {
9187
] {
9288
let input: InputValue = input;
9389
let parsed = BigDecimal::from_input_value(&input);
94-
let expected = BigDecimal::from_str(expected).unwrap();
90+
let expected = expected.parse::<BigDecimal>().unwrap();
9591

9692
assert!(
9793
parsed.is_ok(),
@@ -130,7 +126,7 @@ mod test {
130126
"123",
131127
"43.44",
132128
] {
133-
let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value();
129+
let actual: InputValue = raw.parse::<BigDecimal>().unwrap().to_input_value();
134130

135131
assert_eq!(actual, graphql_input_value!((raw)), "on value: {raw}");
136132
}

0 commit comments

Comments
 (0)