Skip to content

Commit b9e94a6

Browse files
authored
Allow cheap conversion intoScalarValue for custom string types (#1324, #819)
- add `ScalarValue::from_displayable()` method - support `#[value(from_displayable_with = ...)]` attribute in `derive(ScalarValue)` macro - introduce `IntoValue` and `IntoInputValue` conversion traits and replace `Value`'s `From` `impl`s with them
1 parent ea58e78 commit b9e94a6

File tree

15 files changed

+399
-126
lines changed

15 files changed

+399
-126
lines changed

book/src/types/scalars.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,15 @@ 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, ScalarValue, Value};
88+
# use juniper::{GraphQLScalar, IntoValue as _, ScalarValue, Value};
8989
#
9090
#[derive(GraphQLScalar)]
9191
#[graphql(to_output_with = to_output, transparent)]
9292
struct Incremented(i32);
9393

9494
/// Increments [`Incremented`] before converting into a [`Value`].
9595
fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
96-
let inc = v.0 + 1;
97-
Value::from(inc)
96+
(v.0 + 1).into_value()
9897
}
9998
#
10099
# fn main() {}

juniper/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
6464
- Made `name()` method returning `ArcStr`.
6565
- `GraphQLValue`:
6666
- Made `type_name()` method returning `ArcStr`.
67-
- Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([todo])
67+
- Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([20609366])
68+
- Replaced `Value`'s `From` implementations with `IntoValue` ones. ([#1324])
69+
- Replaced `InputValue`'s `From` implementations with `IntoInputValue` ones. ([#1324])
6870

6971
### Added
7072

@@ -78,7 +80,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
7880
- `jiff::tz::Offset` as `UtcOffset` scalar.
7981
- `jiff::Span` as `Duration` scalar.
8082
- `http::GraphQLResponse::into_result()` method. ([#1293])
83+
- `String` scalar implementation for `arcstr::ArcStr`. ([#1247])
8184
- `String` scalar implementation for `compact_str::CompactString`. ([20609366])
85+
- `ScalarValue::from_displayable()` method allowing to specialize `ScalarValue` conversion from custom string types. ([#1324], [#819])
86+
- `IntoValue` and `IntoInputValue` conversion traits allowing to work around orphan rules with custom `ScalarValue`. ([#1324])
8287

8388
### Changed
8489

@@ -103,6 +108,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
103108
[#1293]: /../../pull/1293
104109
[#1311]: /../../pull/1311
105110
[#1318]: /../../pull/1318
111+
[#1324]: /../../pull/1324
106112
[#1325]: /../../pull/1325
107113
[1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295
108114
[20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3

juniper/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ void = { version = "1.0.2", optional = true }
8181
[dev-dependencies]
8282
bencher = "0.1.2"
8383
chrono = { version = "0.4.30", features = ["alloc"], default-features = false }
84+
compact_str = { version = "0.9", features = ["serde"] }
8485
jiff = { version = "0.2", features = ["tzdb-bundle-always"], default-features = false }
8586
pretty_assertions = "1.0.0"
8687
serde_json = "1.0.18"

juniper/src/ast.rs

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{borrow::Cow, fmt, hash::Hash, slice, vec};
22

33
use arcstr::ArcStr;
4+
use compact_str::CompactString;
45

56
use indexmap::IndexMap;
67

@@ -249,10 +250,7 @@ impl<S> InputValue<S> {
249250
}
250251

251252
/// Construct a scalar value
252-
pub fn scalar<T>(v: T) -> Self
253-
where
254-
S: From<T>,
255-
{
253+
pub fn scalar<T: Into<S>>(v: T) -> Self {
256254
Self::Scalar(v.into())
257255
}
258256

@@ -509,51 +507,117 @@ impl<S: ScalarValue> fmt::Display for InputValue<S> {
509507
}
510508
}
511509

512-
impl<S, T> From<Option<T>> for InputValue<S>
510+
/// Conversion into an [`InputValue`].
511+
///
512+
/// This trait exists to work around [orphan rules] and allow to specify custom efficient
513+
/// conversions whenever some custom [`ScalarValue`] is involved
514+
/// (`impl IntoInputValue<CustomScalarValue> for ForeignType` would work, while
515+
/// `impl From<ForeignType> for InputValue<CustomScalarValue>` wound not).
516+
///
517+
/// This trait is used inside [`graphql_input_value!`] macro expansion and implementing it allows to
518+
/// put values of the implementor type there.
519+
///
520+
/// [`graphql_input_value!`]: crate::graphql_input_value
521+
/// [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
522+
pub trait IntoInputValue<S> {
523+
/// Converts this value into an [`InputValue`].
524+
#[must_use]
525+
fn into_input_value(self) -> InputValue<S>;
526+
}
527+
528+
impl<S> IntoInputValue<S> for InputValue<S> {
529+
fn into_input_value(self) -> Self {
530+
self
531+
}
532+
}
533+
534+
impl<T, S> IntoInputValue<S> for Option<T>
513535
where
514-
Self: From<T>,
536+
T: IntoInputValue<S>,
515537
{
516-
fn from(v: Option<T>) -> Self {
517-
match v {
518-
Some(v) => v.into(),
519-
None => Self::Null,
538+
fn into_input_value(self) -> InputValue<S> {
539+
match self {
540+
Some(v) => v.into_input_value(),
541+
None => InputValue::Null,
520542
}
521543
}
522544
}
523545

524-
impl<'a, S: From<String>> From<&'a str> for InputValue<S> {
525-
fn from(s: &'a str) -> Self {
526-
Self::scalar(s.to_owned())
546+
impl<S> IntoInputValue<S> for &str
547+
where
548+
String: Into<S>,
549+
{
550+
fn into_input_value(self) -> InputValue<S> {
551+
InputValue::scalar(self.to_owned())
552+
}
553+
}
554+
555+
impl<S> IntoInputValue<S> for Cow<'_, str>
556+
where
557+
String: Into<S>,
558+
{
559+
fn into_input_value(self) -> InputValue<S> {
560+
InputValue::scalar(self.into_owned())
561+
}
562+
}
563+
564+
impl<S> IntoInputValue<S> for String
565+
where
566+
String: Into<S>,
567+
{
568+
fn into_input_value(self) -> InputValue<S> {
569+
InputValue::scalar(self)
570+
}
571+
}
572+
573+
impl<S: ScalarValue> IntoInputValue<S> for &ArcStr {
574+
fn into_input_value(self) -> InputValue<S> {
575+
InputValue::scalar(S::from_displayable(self))
527576
}
528577
}
529578

530-
impl<'a, S: From<String>> From<Cow<'a, str>> for InputValue<S> {
531-
fn from(s: Cow<'a, str>) -> Self {
532-
Self::scalar(s.into_owned())
579+
impl<S: ScalarValue> IntoInputValue<S> for ArcStr {
580+
fn into_input_value(self) -> InputValue<S> {
581+
(&self).into_input_value()
533582
}
534583
}
535584

536-
impl<S: From<String>> From<String> for InputValue<S> {
537-
fn from(s: String) -> Self {
538-
Self::scalar(s)
585+
impl<S: ScalarValue> IntoInputValue<S> for &CompactString {
586+
fn into_input_value(self) -> InputValue<S> {
587+
InputValue::scalar(S::from_displayable(self))
539588
}
540589
}
541590

542-
impl<S: From<i32>> From<i32> for InputValue<S> {
543-
fn from(i: i32) -> Self {
544-
Self::scalar(i)
591+
impl<S: ScalarValue> IntoInputValue<S> for CompactString {
592+
fn into_input_value(self) -> InputValue<S> {
593+
(&self).into_input_value()
545594
}
546595
}
547596

548-
impl<S: From<f64>> From<f64> for InputValue<S> {
549-
fn from(f: f64) -> Self {
550-
Self::scalar(f)
597+
impl<S> IntoInputValue<S> for i32
598+
where
599+
i32: Into<S>,
600+
{
601+
fn into_input_value(self) -> InputValue<S> {
602+
InputValue::scalar(self)
551603
}
552604
}
553605

554-
impl<S: From<bool>> From<bool> for InputValue<S> {
555-
fn from(b: bool) -> Self {
556-
Self::scalar(b)
606+
impl<S> IntoInputValue<S> for f64
607+
where
608+
f64: Into<S>,
609+
{
610+
fn into_input_value(self) -> InputValue<S> {
611+
InputValue::scalar(self)
612+
}
613+
}
614+
615+
impl<S> IntoInputValue<S> for bool
616+
where
617+
bool: Into<S>,
618+
{
619+
fn into_input_value(self) -> InputValue<S> {
620+
InputValue::scalar(self)
557621
}
558622
}
559623

juniper/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ use crate::{
7474

7575
pub use crate::{
7676
ast::{
77-
Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection,
78-
ToInputValue, Type,
77+
Definition, Document, FromInputValue, InputValue, IntoInputValue, Operation, OperationType,
78+
Selection, ToInputValue, Type,
7979
},
8080
executor::{
8181
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
@@ -102,7 +102,10 @@ pub use crate::{
102102
},
103103
},
104104
validation::RuleError,
105-
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},
105+
value::{
106+
AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue,
107+
ScalarValue, Value,
108+
},
106109
};
107110

108111
/// An error that prevented query execution

juniper/src/macros/graphql_input_value.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,17 +368,17 @@ macro_rules! graphql_input_value {
368368

369369
(None$(,)?) => ($crate::InputValue::null());
370370

371-
(true$(,)?) => ($crate::InputValue::from(true));
371+
(true$(,)?) => ($crate::IntoInputValue::into_input_value(true));
372372

373-
(false$(,)?) => ($crate::InputValue::from(false));
373+
(false$(,)?) => ($crate::IntoInputValue::into_input_value(false));
374374

375375
(@$var:ident$(,)?) => ($crate::InputValue::variable(stringify!($var)));
376376

377377
($enum:ident$(,)?) => ($crate::InputValue::enum_value(stringify!($enum)));
378378

379-
(($e:expr)$(,)?) => ($crate::InputValue::from($e));
379+
(($e:expr)$(,)?) => ($crate::IntoInputValue::into_input_value($e));
380380

381-
($e:expr$(,)?) => ($crate::InputValue::from($e));
381+
($e:expr$(,)?) => ($crate::IntoInputValue::into_input_value($e));
382382
}
383383

384384
#[cfg(test)]

juniper/src/macros/graphql_value.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ macro_rules! graphql_value {
268268

269269
(None$(,)?) => ($crate::Value::null());
270270

271-
($e:expr$(,)?) => ($crate::Value::from($e));
271+
($e:expr$(,)?) => ($crate::IntoValue::into_value($e));
272272
}
273273

274274
#[cfg(test)]

juniper/src/types/scalars.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,12 @@ where
176176
type ArcStr = arcstr::ArcStr;
177177

178178
mod impl_arcstr_scalar {
179-
use crate::{InputValue, ScalarValue, Value};
179+
use crate::{InputValue, IntoValue as _, ScalarValue, Value};
180180

181181
use super::ArcStr;
182182

183183
pub(super) fn to_output<S: ScalarValue>(v: &ArcStr) -> Value<S> {
184-
Value::scalar(v.to_string())
184+
v.into_value()
185185
}
186186

187187
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<ArcStr, String> {
@@ -196,12 +196,12 @@ mod impl_arcstr_scalar {
196196
type CompactString = compact_str::CompactString;
197197

198198
mod impl_compactstring_scalar {
199-
use crate::{InputValue, ScalarValue, Value};
199+
use crate::{InputValue, IntoValue as _, ScalarValue, Value};
200200

201201
use super::CompactString;
202202

203203
pub(super) fn to_output<S: ScalarValue>(v: &CompactString) -> Value<S> {
204-
Value::scalar(v.to_string())
204+
v.into_value()
205205
}
206206

207207
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<CompactString, String> {

0 commit comments

Comments
 (0)