Skip to content

Allow cheap conversion intoScalarValue for custom string types #1324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions book/src/types/scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,15 @@ 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 = <fn path>)]` attribute is the way to do so:
```rust
# extern crate juniper;
# use juniper::{GraphQLScalar, ScalarValue, Value};
# use juniper::{GraphQLScalar, IntoValue as _, ScalarValue, Value};
#
#[derive(GraphQLScalar)]
#[graphql(to_output_with = to_output, transparent)]
struct Incremented(i32);

/// Increments [`Incremented`] before converting into a [`Value`].
fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
let inc = v.0 + 1;
Value::from(inc)
(v.0 + 1).into_value()
}
#
# fn main() {}
Expand Down
8 changes: 7 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Made `name()` method returning `ArcStr`.
- `GraphQLValue`:
- Made `type_name()` method returning `ArcStr`.
- Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([todo])
- Switched `ParseError::UnexpectedToken` to `compact_str::CompactString` instead of `smartstring::SmartString`. ([20609366])
- Replaced `Value`'s `From` implementations with `IntoValue` ones. ([#1324])
- Replaced `InputValue`'s `From` implementations with `IntoInputValue` ones. ([#1324])

### Added

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

### Changed

Expand All @@ -103,6 +108,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1293]: /../../pull/1293
[#1311]: /../../pull/1311
[#1318]: /../../pull/1318
[#1324]: /../../pull/1324
[#1325]: /../../pull/1325
[1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295
[20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3
Expand Down
1 change: 1 addition & 0 deletions juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ void = { version = "1.0.2", optional = true }
[dev-dependencies]
bencher = "0.1.2"
chrono = { version = "0.4.30", features = ["alloc"], default-features = false }
compact_str = { version = "0.9", features = ["serde"] }
jiff = { version = "0.2", features = ["tzdb-bundle-always"], default-features = false }
pretty_assertions = "1.0.0"
serde_json = "1.0.18"
Expand Down
120 changes: 92 additions & 28 deletions juniper/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{borrow::Cow, fmt, hash::Hash, slice, vec};

use arcstr::ArcStr;
use compact_str::CompactString;

use indexmap::IndexMap;

Expand Down Expand Up @@ -249,10 +250,7 @@ impl<S> InputValue<S> {
}

/// Construct a scalar value
pub fn scalar<T>(v: T) -> Self
where
S: From<T>,
{
pub fn scalar<T: Into<S>>(v: T) -> Self {
Self::Scalar(v.into())
}

Expand Down Expand Up @@ -509,51 +507,117 @@ impl<S: ScalarValue> fmt::Display for InputValue<S> {
}
}

impl<S, T> From<Option<T>> for InputValue<S>
/// Conversion into an [`InputValue`].
///
/// This trait exists to work around [orphan rules] and allow to specify custom efficient
/// conversions whenever some custom [`ScalarValue`] is involved
/// (`impl IntoInputValue<CustomScalarValue> for ForeignType` would work, while
/// `impl From<ForeignType> for InputValue<CustomScalarValue>` wound not).
///
/// This trait is used inside [`graphql_input_value!`] macro expansion and implementing it allows to
/// put values of the implementor type there.
///
/// [`graphql_input_value!`]: crate::graphql_input_value
/// [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
pub trait IntoInputValue<S> {
/// Converts this value into an [`InputValue`].
#[must_use]
fn into_input_value(self) -> InputValue<S>;
}

impl<S> IntoInputValue<S> for InputValue<S> {
fn into_input_value(self) -> Self {
self
}
}

impl<T, S> IntoInputValue<S> for Option<T>
where
Self: From<T>,
T: IntoInputValue<S>,
{
fn from(v: Option<T>) -> Self {
match v {
Some(v) => v.into(),
None => Self::Null,
fn into_input_value(self) -> InputValue<S> {
match self {
Some(v) => v.into_input_value(),
None => InputValue::Null,
}
}
}

impl<'a, S: From<String>> From<&'a str> for InputValue<S> {
fn from(s: &'a str) -> Self {
Self::scalar(s.to_owned())
impl<S> IntoInputValue<S> for &str
where
String: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self.to_owned())
}
}

impl<S> IntoInputValue<S> for Cow<'_, str>
where
String: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self.into_owned())
}
}

impl<S> IntoInputValue<S> for String
where
String: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self)
}
}

impl<S: ScalarValue> IntoInputValue<S> for &ArcStr {
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(S::from_displayable(self))
}
}

impl<'a, S: From<String>> From<Cow<'a, str>> for InputValue<S> {
fn from(s: Cow<'a, str>) -> Self {
Self::scalar(s.into_owned())
impl<S: ScalarValue> IntoInputValue<S> for ArcStr {
fn into_input_value(self) -> InputValue<S> {
(&self).into_input_value()
}
}

impl<S: From<String>> From<String> for InputValue<S> {
fn from(s: String) -> Self {
Self::scalar(s)
impl<S: ScalarValue> IntoInputValue<S> for &CompactString {
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(S::from_displayable(self))
}
}

impl<S: From<i32>> From<i32> for InputValue<S> {
fn from(i: i32) -> Self {
Self::scalar(i)
impl<S: ScalarValue> IntoInputValue<S> for CompactString {
fn into_input_value(self) -> InputValue<S> {
(&self).into_input_value()
}
}

impl<S: From<f64>> From<f64> for InputValue<S> {
fn from(f: f64) -> Self {
Self::scalar(f)
impl<S> IntoInputValue<S> for i32
where
i32: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self)
}
}

impl<S: From<bool>> From<bool> for InputValue<S> {
fn from(b: bool) -> Self {
Self::scalar(b)
impl<S> IntoInputValue<S> for f64
where
f64: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self)
}
}

impl<S> IntoInputValue<S> for bool
where
bool: Into<S>,
{
fn into_input_value(self) -> InputValue<S> {
InputValue::scalar(self)
}
}

Expand Down
9 changes: 6 additions & 3 deletions juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ use crate::{

pub use crate::{
ast::{
Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection,
ToInputValue, Type,
Definition, Document, FromInputValue, InputValue, IntoInputValue, Operation, OperationType,
Selection, ToInputValue, Type,
},
executor::{
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
Expand All @@ -102,7 +102,10 @@ pub use crate::{
},
},
validation::RuleError,
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},
value::{
AnyExt, DefaultScalarValue, IntoValue, Object, ParseScalarResult, ParseScalarValue,
ScalarValue, Value,
},
};

/// An error that prevented query execution
Expand Down
8 changes: 4 additions & 4 deletions juniper/src/macros/graphql_input_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,17 +368,17 @@ macro_rules! graphql_input_value {

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

(true$(,)?) => ($crate::InputValue::from(true));
(true$(,)?) => ($crate::IntoInputValue::into_input_value(true));

(false$(,)?) => ($crate::InputValue::from(false));
(false$(,)?) => ($crate::IntoInputValue::into_input_value(false));

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

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

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

($e:expr$(,)?) => ($crate::InputValue::from($e));
($e:expr$(,)?) => ($crate::IntoInputValue::into_input_value($e));
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/macros/graphql_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ macro_rules! graphql_value {

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

($e:expr$(,)?) => ($crate::Value::from($e));
($e:expr$(,)?) => ($crate::IntoValue::into_value($e));
}

#[cfg(test)]
Expand Down
8 changes: 4 additions & 4 deletions juniper/src/types/scalars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ where
type ArcStr = arcstr::ArcStr;

mod impl_arcstr_scalar {
use crate::{InputValue, ScalarValue, Value};
use crate::{InputValue, IntoValue as _, ScalarValue, Value};

use super::ArcStr;

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

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

mod impl_compactstring_scalar {
use crate::{InputValue, ScalarValue, Value};
use crate::{InputValue, IntoValue as _, ScalarValue, Value};

use super::CompactString;

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

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