Skip to content

Commit c7b5fe9

Browse files
authored
Strip InputValue from from_input() function for GraphQL scalars (#1327)
- remove `as_float_value()`, `as_string_value()` and `as_scalar_value()` methods from `Value` enum - remove `as_float_value()`, `as_int_value()`, `as_string_value()` and `as_scalar_value()` methods from `InputValue` enum - `ScalarValue` trait: - switch from `From` conversions to `TryScalarValueTo` conversions - require `TryScalarValueTo` conversions for `bool`, `f64`, `i32`, `String` and `&str` types - require `TryInto<String>` conversion - make `is_type()` method required and accepting `Any` type - rename `as_bool()` method as `try_to_bool()` and make it defined by default as `TryScalarValueTo<bool>` alias - rename `as_float()` method as `try_to_float()` and make it defined by default as `TryScalarValueTo<f64>` alias - rename `as_int()` method as `try_to_int()` and make it defined by default as `TryScalarValueTo<i32>` alias - rename `as_string()` method as `try_to_string()` and make it defined by default as `TryScalarValueTo<String>` alias - rename `as_str()` method as `try_as_str()` and make it defined by default as `TryScalarValueTo<&str>` alias - rename `into_string()` method as `try_into_string()` and make it defined by default as `TryInto<String>` alias - add `try_to::<T>()` method defined by default as `TryScalarValueTo<T>` alias - add `TryScalarValueTo` conversion trait aiding `ScalarValue` trait - `#[derive(ScalarValue)]` macro: - rename `#[value(as_bool)]` attribute as `#[value(to_bool)]` - rename `#[value(as_float)]` attribute as `#[value(to_float)]` - rename `#[value(as_int)]` attribute as `#[value(to_int)]` - rename `#[value(as_string)]` attribute as `#[value(to_string)]` - remove `#[value(into_string)]` attribute - remove `#[value(allow_missing_attributes)]` attribute (now attributes can always be omitted) - `From` and `Display` implementations are not derived anymore - `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: - make provided `from_input()` function to accept `ScalarValue` (or anything `TryScalarValueTo`-convertible) directly instead of `InputValue` - support non-`Result` return type in `from_input()` function - add `Scalar` transparent wrapper for aiding type inference in `from_input()` function when input argument is generic `ScalarValue`
1 parent 20ff0ea commit c7b5fe9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1154
-1206
lines changed

book/src/types/scalars.md

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -105,31 +105,57 @@ fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
105105
Customization of a [custom GraphQL scalar][2] value parsing is possible via `#[graphql(from_input_with = <fn path>)]` attribute:
106106
```rust
107107
# extern crate juniper;
108-
# use juniper::{GraphQLScalar, InputValue, ScalarValue};
108+
# use juniper::{GraphQLScalar, ScalarValue};
109109
#
110110
#[derive(GraphQLScalar)]
111111
#[graphql(from_input_with = Self::from_input, transparent)]
112112
struct UserId(String);
113113

114114
impl UserId {
115-
/// Checks whether the [`InputValue`] is a [`String`] beginning with `id: `
116-
/// and strips it.
117-
fn from_input<S>(input: &InputValue<S>) -> Result<Self, String>
118-
where
119-
S: ScalarValue
120-
{
121-
input.as_string_value()
122-
.ok_or_else(|| format!("Expected `String`, found: {input}"))
123-
.and_then(|str| {
124-
str.strip_prefix("id: ")
125-
.ok_or_else(|| {
126-
format!(
127-
"Expected `UserId` to begin with `id: `, \
128-
found: {input}",
129-
)
130-
})
115+
/// Checks whether the [`InputValue`] is a [`String`] beginning with `id: ` and strips it.
116+
fn from_input(
117+
input: &str,
118+
// ^^^^ any concrete type having `TryScalarValueTo` implementation could be used
119+
) -> Result<Self, Box<str>> {
120+
// ^^^^^^^^ must implement `IntoFieldError`
121+
input
122+
.strip_prefix("id: ")
123+
.ok_or_else(|| {
124+
format!("Expected `UserId` to begin with `id: `, found: {input}").into()
131125
})
132-
.map(|id| Self(id.to_owned()))
126+
.map(|id| Self(id.into()))
127+
}
128+
}
129+
#
130+
# fn main() {}
131+
```
132+
133+
The provided function is polymorphic by input and output types:
134+
```rust
135+
# extern crate juniper;
136+
# use juniper::{GraphQLScalar, Scalar, ScalarValue};
137+
#
138+
#[derive(GraphQLScalar)]
139+
#[graphql(from_input_with = Self::from_input, transparent)]
140+
struct UserId(String);
141+
142+
impl UserId {
143+
fn from_input(
144+
input: &Scalar<impl ScalarValue>,
145+
// ^^^^^^ for generic argument using `Scalar` transparent wrapper is required,
146+
// otherwise Rust won't be able to infer the required type
147+
) -> Self {
148+
// ^^^^ if the result is infallible, it's OK to not use `Result`
149+
Self(
150+
input
151+
.try_to_int().map(|i| i.to_string())
152+
.or_else(|| input.try_to_bool().map(|f| f.to_string()))
153+
.or_else(|| input.try_to_float().map(|b| b.to_string()))
154+
.or_else(|| input.try_to_string())
155+
.unwrap_or_else(|| {
156+
unreachable!("`ScalarValue` is at least one of primitive GraphQL types")
157+
}),
158+
)
133159
}
134160
}
135161
#
@@ -143,8 +169,7 @@ Customization of which tokens a [custom GraphQL scalar][0] type should be parsed
143169
```rust
144170
# extern crate juniper;
145171
# use juniper::{
146-
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
147-
# ScalarValue, ScalarToken, Value,
172+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
148173
# };
149174
#
150175
#[derive(GraphQLScalar)]
@@ -168,11 +193,11 @@ fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
168193
}
169194
}
170195

171-
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
172-
v.as_string_value()
173-
.map(|s| StringOrInt::String(s.into()))
174-
.or_else(|| v.as_int_value().map(StringOrInt::Int))
175-
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
196+
fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
197+
v.try_to_string()
198+
.map(StringOrInt::String)
199+
.or_else(|| v.try_to_int().map(StringOrInt::Int))
200+
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
176201
}
177202

178203
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
@@ -191,8 +216,7 @@ Instead of providing all custom functions separately, it's possible to provide a
191216
```rust
192217
# extern crate juniper;
193218
# use juniper::{
194-
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
195-
# ScalarValue, ScalarToken, Value,
219+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
196220
# };
197221
#
198222
#[derive(GraphQLScalar)]
@@ -212,11 +236,11 @@ mod string_or_int {
212236
}
213237
}
214238

215-
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
216-
v.as_string_value()
217-
.map(|s| StringOrInt::String(s.into()))
218-
.or_else(|| v.as_int_value().map(StringOrInt::Int))
219-
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
239+
pub(super) fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
240+
v.try_to_string()
241+
.map(StringOrInt::String)
242+
.or_else(|| v.try_to_int().map(StringOrInt::Int))
243+
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
220244
}
221245

222246
pub(super) fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<S> {
@@ -232,8 +256,7 @@ A regular `impl` block is also suitable for that:
232256
```rust
233257
# extern crate juniper;
234258
# use juniper::{
235-
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
236-
# ScalarValue, ScalarToken, Value,
259+
# GraphQLScalar, ParseScalarResult, ParseScalarValue, Scalar, ScalarToken, ScalarValue, Value,
237260
# };
238261
#
239262
#[derive(GraphQLScalar)]
@@ -251,14 +274,11 @@ impl StringOrInt {
251274
}
252275
}
253276

254-
fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
255-
where
256-
S: ScalarValue
257-
{
258-
v.as_string_value()
259-
.map(|s| Self::String(s.into()))
260-
.or_else(|| v.as_int_value().map(Self::Int))
261-
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
277+
fn from_input(v: &Scalar<impl ScalarValue>) -> Result<Self, Box<str>> {
278+
v.try_to_string()
279+
.map(Self::String)
280+
.or_else(|| v.try_to_int().map(Self::Int))
281+
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
262282
}
263283

264284
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<S>
@@ -277,8 +297,7 @@ At the same time, any custom function still may be specified separately, if requ
277297
```rust
278298
# extern crate juniper;
279299
# use juniper::{
280-
# GraphQLScalar, InputValue, ParseScalarResult, ScalarValue,
281-
# ScalarToken, Value
300+
# GraphQLScalar, ParseScalarResult, Scalar, ScalarToken, ScalarValue, Value,
282301
# };
283302
#
284303
#[derive(GraphQLScalar)]
@@ -304,14 +323,11 @@ mod string_or_int {
304323
}
305324
}
306325

307-
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
308-
where
309-
S: ScalarValue,
310-
{
311-
v.as_string_value()
312-
.map(|s| StringOrInt::String(s.into()))
313-
.or_else(|| v.as_int_value().map(StringOrInt::Int))
314-
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
326+
pub(super) fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
327+
v.try_to_string()
328+
.map(StringOrInt::String)
329+
.or_else(|| v.try_to_int().map(StringOrInt::Int))
330+
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
315331
}
316332

317333
// No need in `parse_token()` function.
@@ -351,9 +367,10 @@ For implementing [custom scalars][2] on foreign types there is [`#[graphql_scala
351367
# }
352368
#
353369
# use juniper::DefaultScalarValue as CustomScalarValue;
354-
use juniper::{InputValue, ScalarValue, Value, graphql_scalar};
370+
use juniper::{ScalarValue, Value, graphql_scalar};
355371

356-
#[graphql_scalar(
372+
#[graphql_scalar]
373+
#[graphql(
357374
with = date_scalar,
358375
parse_token(String),
359376
scalar = CustomScalarValue,
@@ -369,10 +386,8 @@ mod date_scalar {
369386
Value::scalar(v.to_string())
370387
}
371388

372-
pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
373-
v.as_string_value()
374-
.ok_or_else(|| format!("Expected `String`, found: {v}"))
375-
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {e}")))
389+
pub(super) fn from_input(s: &str) -> Result<Date, Box<str>> {
390+
s.parse().map_err(|e| format!("Failed to parse `Date`: {e}").into())
376391
}
377392
}
378393
#

juniper/CHANGELOG.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,31 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
6767
- Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([20609366])
6868
- Replaced `Value`'s `From` implementations with `IntoValue` ones. ([#1324])
6969
- Replaced `InputValue`'s `From` implementations with `IntoInputValue` ones. ([#1324])
70+
- `Value` enum: ([#1327])
71+
- Removed `as_float_value()`, `as_string_value()` and `as_scalar_value()` methods (use `as_scalar()` method and then `ScalarValue` methods instead).
72+
- `InputValue` enum: ([#1327])
73+
- Removed `as_float_value()`, `as_int_value()`, `as_string_value()` and `as_scalar_value()` methods (use `as_scalar()` method and then `ScalarValue` methods instead).
74+
- `ScalarValue` trait: ([#1327])
75+
- Switched from `From` conversions to `TryScalarValueTo` conversions.
76+
- Made to require `TryScalarValueTo` conversions for `bool`, `f64`, `i32`, `String` and `&str` types (could be derived with `#[value(<conversion>)]` attributes of `#[derive(ScalarValue)]` macro).
77+
- Made to require `TryInto<String>` conversion (could be derived with `derive_more::TryInto`).
78+
- Made `is_type()` method required and to accept `Any` type.
79+
- Renamed `as_bool()` method as `try_to_bool()` and made it defined by default as `TryScalarValueTo<bool>` alias.
80+
- Renamed `as_float()` method as `try_to_float()` and made it defined by default as `TryScalarValueTo<f64>` alias.
81+
- Renamed `as_int()` method as `try_to_int()` and made it defined by default as `TryScalarValueTo<i32>` alias.
82+
- Renamed `as_string()` method as `try_to_string()` and made it defined by default as `TryScalarValueTo<String>` alias.
83+
- Renamed `as_str()` method as `try_as_str()` and made it defined by default as `TryScalarValueTo<&str>` alias.
84+
- Renamed `into_string()` method as `try_into_string()` and made it defined by default as `TryInto<String>` alias.
85+
- `#[derive(ScalarValue)]` macro: ([#1327])
86+
- Renamed `#[value(as_bool)]` attribute as `#[value(to_bool)]`.
87+
- Renamed `#[value(as_float)]` attribute as `#[value(to_float)]`.
88+
- Renamed `#[value(as_int)]` attribute as `#[value(to_int)]`.
89+
- Renamed `#[value(as_string)]` attribute as `#[value(to_string)]`.
90+
- Removed `#[value(into_string)]` attribute.
91+
- Removed `#[value(allow_missing_attributes)]` attribute (now attributes can always be omitted).
92+
- `From` and `Display` implementations are not derived anymore (recommended way is to use [`derive_more` crate] for this).
93+
- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327])
94+
- Made provided `from_input()` function to accept `ScalarValue` (or anything `TryScalarValueTo`-convertible) directly instead of `InputValue`.
7095

7196
### Added
7297

@@ -82,8 +107,15 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
82107
- `http::GraphQLResponse::into_result()` method. ([#1293])
83108
- `String` scalar implementation for `arcstr::ArcStr`. ([#1247])
84109
- `String` scalar implementation for `compact_str::CompactString`. ([20609366])
85-
- `ScalarValue::from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819])
86110
- `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324])
111+
- `ScalarValue` trait:
112+
- `from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819])
113+
- `try_to::<T>()` method defined by default as `TryScalarValueTo<T>` alias. ([#1327])
114+
- `TryScalarValueTo` conversion trait aiding `ScalarValue` trait. ([#1327])
115+
- `#[derive(GraphQLScalar)]` and `#[graphql_scalar]` macros: ([#1327])
116+
- Support for specifying concrete types as input argument in provided `from_input()` function.
117+
- Support for non-`Result` return type in provided `from_input()` function.
118+
- `Scalar` transparent wrapper for aiding type inference in `from_input()` function when input argument is generic `ScalarValue`.
87119

88120
### Changed
89121

@@ -110,6 +142,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
110142
[#1318]: /../../pull/1318
111143
[#1324]: /../../pull/1324
112144
[#1326]: /../../pull/1326
145+
[#1327]: /../../pull/1327
113146
[1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295
114147
[20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3
115148

@@ -313,6 +346,7 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md).
313346
[`bson` crate]: https://docs.rs/bson
314347
[`chrono` crate]: https://docs.rs/chrono
315348
[`chrono-tz` crate]: https://docs.rs/chrono-tz
349+
[`derive_more` crate]: https://docs.rs/derive_more
316350
[`jiff` crate]: https://docs.rs/jiff
317351
[`time` crate]: https://docs.rs/time
318352
[Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html

juniper/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ bson = { version = "2.4", optional = true }
5151
chrono = { version = "0.4.30", features = ["alloc"], default-features = false, optional = true }
5252
chrono-tz = { version = "0.10", default-features = false, optional = true }
5353
compact_str = "0.9"
54-
derive_more = { version = "2.0", features = ["debug", "deref", "display", "error", "from", "into", "into_iterator"] }
54+
derive_more = { version = "2.0", features = ["debug", "deref", "display", "error", "from", "into", "into_iterator", "try_into"] }
5555
fnv = "1.0.5"
5656
futures = { version = "0.3.22", features = ["alloc"], default-features = false }
5757
graphql-parser = { version = "0.4", optional = true }
5858
indexmap = { version = "2.0", features = ["serde"] }
5959
itertools = "0.14"
6060
jiff = { version = "0.2", features = ["std"], default-features = false, optional = true }
6161
juniper_codegen = { version = "0.16.0", path = "../juniper_codegen" }
62+
ref-cast = "1.0"
6263
rust_decimal = { version = "1.20", default-features = false, optional = true }
6364
ryu = { version = "1.0", optional = true }
6465
serde = { version = "1.0.122", features = ["derive"] }

juniper/src/ast.rs

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use indexmap::IndexMap;
88
use crate::{
99
executor::Variables,
1010
parser::Spanning,
11-
value::{DefaultScalarValue, ScalarValue},
11+
value::{DefaultScalarValue, ScalarValue, ScalarValueFmt},
1212
};
1313

1414
/// Type literal in a syntax tree.
@@ -359,30 +359,6 @@ impl<S> InputValue<S> {
359359
}
360360
}
361361

362-
/// View the underlying int value, if present.
363-
pub fn as_int_value(&self) -> Option<i32>
364-
where
365-
S: ScalarValue,
366-
{
367-
self.as_scalar_value().and_then(|s| s.as_int())
368-
}
369-
370-
/// View the underlying float value, if present.
371-
pub fn as_float_value(&self) -> Option<f64>
372-
where
373-
S: ScalarValue,
374-
{
375-
self.as_scalar_value().and_then(|s| s.as_float())
376-
}
377-
378-
/// View the underlying string value, if present.
379-
pub fn as_string_value(&self) -> Option<&str>
380-
where
381-
S: ScalarValue,
382-
{
383-
self.as_scalar_value().and_then(|s| s.as_str())
384-
}
385-
386362
/// View the underlying scalar value, if present.
387363
pub fn as_scalar(&self) -> Option<&S> {
388364
match self {
@@ -391,15 +367,6 @@ impl<S> InputValue<S> {
391367
}
392368
}
393369

394-
/// View the underlying scalar value, if present.
395-
pub fn as_scalar_value<'a, T>(&'a self) -> Option<&'a T>
396-
where
397-
T: 'a,
398-
Option<&'a T>: From<&'a S>,
399-
{
400-
self.as_scalar().and_then(Into::into)
401-
}
402-
403370
/// Converts this [`InputValue`] to a [`Spanning::unlocated`] object value.
404371
///
405372
/// This constructs a new [`IndexMap`] containing references to the keys
@@ -473,13 +440,7 @@ impl<S: ScalarValue> fmt::Display for InputValue<S> {
473440
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474441
match self {
475442
Self::Null => write!(f, "null"),
476-
Self::Scalar(s) => {
477-
if let Some(s) = s.as_str() {
478-
write!(f, "\"{s}\"")
479-
} else {
480-
write!(f, "{s}")
481-
}
482-
}
443+
Self::Scalar(s) => fmt::Display::fmt(&ScalarValueFmt(s), f),
483444
Self::Enum(v) => write!(f, "{v}"),
484445
Self::Variable(v) => write!(f, "${v}"),
485446
Self::List(v) => {

juniper/src/executor/look_ahead.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ impl<'a, S: ScalarValue> ChildrenBuilder<'a, '_, S> {
772772
LookAheadValue::from_input_value(v.as_ref(), Some(self.vars))
773773
.item
774774
{
775-
s.as_bool().unwrap_or(false)
775+
s.try_to_bool().unwrap_or(false)
776776
} else {
777777
false
778778
}
@@ -788,7 +788,7 @@ impl<'a, S: ScalarValue> ChildrenBuilder<'a, '_, S> {
788788
LookAheadValue::from_input_value(v.as_ref(), Some(self.vars))
789789
.item
790790
{
791-
b.as_bool().map(Not::not).unwrap_or(false)
791+
b.try_to_bool().map(Not::not).unwrap_or(false)
792792
} else {
793793
false
794794
}

0 commit comments

Comments
 (0)