Skip to content

Commit

Permalink
Fix a bug regarding to input value casting in custom scalars
Browse files Browse the repository at this point in the history
This fix introduces a new way to convert a scalar value into one of
the 4 standardized scalar values (Int, Float, String, Bool). Using
this abstraction it is possible to do certain needed conversations in
custom scalar value code, for example that one that converts a integer
value to a floating point value.
  • Loading branch information
weiznich committed Sep 28, 2018
1 parent 03dfdd6 commit 11021ea
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 45 deletions.
16 changes: 6 additions & 10 deletions juniper/src/types/scalars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ graphql_scalar!(ID as "ID" where Scalar = <S>{
from_input_value(v: &InputValue) -> Option<ID> {
match *v {
InputValue::Scalar(ref s) => {
<_ as Into<Option<String>>>::into(s.clone()).or_else(||{
<_ as Into<Option<i32>>>::into(s.clone()).map(|i| i.to_string())
}).map(ID)
s.as_string().or_else(|| s.as_int().map(|i| i.to_string()))
.map(ID)
}
_ => None
}
Expand All @@ -64,7 +63,7 @@ graphql_scalar!(String as "String" where Scalar = <S>{

from_input_value(v: &InputValue) -> Option<String> {
match *v {
InputValue::Scalar(ref s) => <_ as Into<Option<String>>>::into(s.clone()),
InputValue::Scalar(ref s) => s.as_string(),
_ => None,
}
}
Expand Down Expand Up @@ -202,7 +201,7 @@ graphql_scalar!(bool as "Boolean" where Scalar = <S>{

from_input_value(v: &InputValue) -> Option<bool> {
match *v {
InputValue::Scalar(ref b) => <_ as Into<Option<& bool>>>::into(b).map(|b| *b),
InputValue::Scalar(ref b) => b.as_boolean(),
_ => None,
}
}
Expand All @@ -221,7 +220,7 @@ graphql_scalar!(i32 as "Int" where Scalar = <S>{

from_input_value(v: &InputValue) -> Option<i32> {
match *v {
InputValue::Scalar(ref i) => <_ as Into<Option<&i32>>>::into(i).map(|i| *i),
InputValue::Scalar(ref i) => i.as_int(),
_ => None,
}
}
Expand All @@ -244,10 +243,7 @@ graphql_scalar!(f64 as "Float" where Scalar = <S>{

from_input_value(v: &InputValue) -> Option<f64> {
match *v {
InputValue::Scalar(ref s) => {
<_ as Into<Option<&f64>>>::into(s).map(|i| *i)
.or_else(|| <_ as Into<Option<&i32>>>::into(s).map(|i| *i as f64))
}
InputValue::Scalar(ref s) => s.as_float(),
_ => None,
}
}
Expand Down
102 changes: 99 additions & 3 deletions juniper/src/value/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
/// # extern crate juniper;
/// # extern crate serde;
/// # use serde::{de, Deserialize, Deserializer};
/// # use juniper::ScalarValue;
/// # use std::fmt;
/// #
/// #[derive(Debug, Clone, PartialEq, ScalarValue)]
/// #[juniper(visitor = "MyScalarValueVisitor")]
/// enum MyScalarValue {
/// Int(i32),
/// Long(i64),
Expand All @@ -47,6 +47,39 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
/// Boolean(bool),
/// }
///
/// impl ScalarValue for MyScalarValue {
/// type Visitor = MyScalarValueVisitor;
///
/// fn as_int(&self) -> Option<i32> {
/// match *self {
/// MyScalarValue::Int(ref i) => Some(*i),
/// _ => None,
/// }
/// }
///
/// fn as_string(&self) -> Option<String> {
/// match *self {
/// MyScalarValue::String(ref s) => Some(s.clone()),
/// _ => None,
/// }
/// }
///
/// fn as_float(&self) -> Option<f64> {
/// match *self {
/// MyScalarValue::Int(ref i) => Some(*i as f64),
/// MyScalarValue::Float(ref f) => Some(*f),
/// _ => None,
/// }
/// }
///
/// fn as_boolean(&self) -> Option<bool> {
/// match *self {
/// MyScalarValue::Boolean(ref b) => Some(*b),
/// _ => None,
/// }
/// }
/// }
///
/// #[derive(Default)]
/// struct MyScalarValueVisitor;
///
Expand All @@ -72,7 +105,11 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
/// where
/// E: de::Error,
/// {
/// Ok(MyScalarValue::Long(value))
/// if value <= i32::max_value() as i64 {
/// self.visit_i32(value as i32)
/// } else {
/// Ok(MyScalarValue::Long(value))
/// }
/// }
///
/// fn visit_u32<E>(self, value: u32) -> Result<MyScalarValue, E>
Expand Down Expand Up @@ -156,6 +193,33 @@ pub trait ScalarValue:
{
self.into().is_some()
}

/// Convert the given scalar value into an integer value
///
/// This function is used for implementing `GraphQLType` for `i32` for all
/// scalar values. Implementations should convert all supported integer
/// types with 32 bit or less to an integer if requested.
fn as_int(&self) -> Option<i32>;

/// Convert the given scalar value into a string value
///
/// This function is used for implementing `GraphQLType` for `String` for all
/// scalar values
fn as_string(&self) -> Option<String>;

/// Convert the given scalar value into a float value
///
/// This function is used for implementing `GraphQLType` for `f64` for all
/// scalar values. Implementations should convert all supported integer
/// types with 64 bit or less and all floating point values with 64 bit or
/// less to a float if requested.
fn as_float(&self) -> Option<f64>;

/// Convert the given scalar value into a boolean value
///
/// This function is used for implementing `GraphQLType` for `bool` for all
/// scalar values.
fn as_boolean(&self) -> Option<bool>;
}

/// A marker trait extending the [`ScalarValue`](../trait.ScalarValue.html) trait
Expand Down Expand Up @@ -189,14 +253,46 @@ where
/// This types closely follows the graphql specification.
#[derive(Debug, PartialEq, Clone, ScalarValue)]
#[allow(missing_docs)]
#[juniper(visitor = "DefaultScalarValueVisitor")]
pub enum DefaultScalarValue {
Int(i32),
Float(f64),
String(String),
Boolean(bool),
}

impl ScalarValue for DefaultScalarValue {
type Visitor = DefaultScalarValueVisitor;

fn as_int(&self) -> Option<i32> {
match *self {
DefaultScalarValue::Int(ref i) => Some(*i),
_ => None,
}
}

fn as_string(&self) -> Option<String> {
match *self {
DefaultScalarValue::String(ref s) => Some(s.clone()),
_ => None,
}
}

fn as_float(&self) -> Option<f64> {
match *self {
DefaultScalarValue::Int(ref i) => Some(*i as f64),
DefaultScalarValue::Float(ref f) => Some(*f),
_ => None,
}
}

fn as_boolean(&self) -> Option<bool> {
match *self {
DefaultScalarValue::Boolean(ref b) => Some(*b),
_ => None,
}
}
}

impl<'a> From<&'a str> for DefaultScalarValue {
fn from(s: &'a str) -> Self {
DefaultScalarValue::String(s.into())
Expand Down
19 changes: 0 additions & 19 deletions juniper_codegen/src/derive_juniper_scalar_value.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
use proc_macro2::{Span, TokenStream};

use syn::{self, Data, Fields, Ident, Variant};
use util::{get_juniper_attr, keyed_item_value, AttributeValidation, AttributeValue};

pub fn impl_scalar_value(ast: &syn::DeriveInput) -> TokenStream {
let ident = &ast.ident;
let visitor = if let Some(items) = get_juniper_attr(&ast.attrs) {
items.into_iter()
.filter_map(|i| keyed_item_value(&i, "visitor", AttributeValidation::String))
.next()
.and_then(|t| if let AttributeValue::String(s) = t {
Some(Ident::new(&s, Span::call_site()))
} else {
None
})
.expect("`#[derive(ScalarValue)]` needs a annotation of the form `#[juniper(visitor = \"VisitorType\")]`")
} else {
panic!("`#[derive(ScalarValue)]` needs a annotation of the form `#[juniper(visitor = \"VisitorType\")]`");
};

let variants = match ast.data {
Data::Enum(ref enum_data) => &enum_data.variants,
Expand Down Expand Up @@ -54,11 +40,6 @@ pub fn impl_scalar_value(ast: &syn::DeriveInput) -> TokenStream {

#serialize
#display

impl juniper::ScalarValue for #ident {
type Visitor = #visitor;
}

};
}
}
Expand Down
2 changes: 1 addition & 1 deletion juniper_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
gen.into()
}

#[proc_macro_derive(ScalarValue, attributes(juniper))]
#[proc_macro_derive(ScalarValue)]
pub fn derive_juniper_scalar_value(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
let gen = derive_juniper_scalar_value::impl_scalar_value(&ast);
Expand Down
10 changes: 1 addition & 9 deletions juniper_codegen/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,9 @@ fn get_doc_attr(attrs: &Vec<Attribute>) -> Option<Vec<MetaNameValue>> {

// Get the nested items of a a #[graphql(...)] attribute.
pub fn get_graphql_attr(attrs: &Vec<Attribute>) -> Option<Vec<NestedMeta>> {
get_named_attr(attrs, "graphql")
}

pub fn get_juniper_attr(attrs: &Vec<Attribute>) -> Option<Vec<NestedMeta>> {
get_named_attr(attrs, "juniper")
}

pub fn get_named_attr(attrs: &Vec<Attribute>, name: &str) -> Option<Vec<NestedMeta>> {
for attr in attrs {
match attr.interpret_meta() {
Some(Meta::List(ref list)) if list.ident == name => {
Some(Meta::List(ref list)) if list.ident == "graphql" => {
return Some(list.nested.iter().map(|x| x.clone()).collect());
}
_ => {}
Expand Down
42 changes: 39 additions & 3 deletions juniper_tests/src/custom_scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ use juniper::parser::{ParseError, ScalarToken, Token};
use juniper::serde::de;
#[cfg(test)]
use juniper::{execute, EmptyMutation, Object, RootNode, Variables};
use juniper::{InputValue, ParseScalarResult, Value};
use juniper::{InputValue, ParseScalarResult, ScalarValue, Value};
use std::fmt;

#[derive(Debug, Clone, PartialEq, ScalarValue)]
#[juniper(visitor = "MyScalarValueVisitor")]
enum MyScalarValue {
Int(i32),
Long(i64),
Expand All @@ -19,6 +18,39 @@ enum MyScalarValue {
Boolean(bool),
}

impl ScalarValue for MyScalarValue {
type Visitor = MyScalarValueVisitor;

fn as_int(&self) -> Option<i32> {
match *self {
MyScalarValue::Int(ref i) => Some(*i),
_ => None,
}
}

fn as_string(&self) -> Option<String> {
match *self {
MyScalarValue::String(ref s) => Some(s.clone()),
_ => None,
}
}

fn as_float(&self) -> Option<f64> {
match *self {
MyScalarValue::Int(ref i) => Some(*i as f64),
MyScalarValue::Float(ref f) => Some(*f),
_ => None,
}
}

fn as_boolean(&self) -> Option<bool> {
match *self {
MyScalarValue::Boolean(ref b) => Some(*b),
_ => None,
}
}
}

#[derive(Default, Debug)]
struct MyScalarValueVisitor;

Expand All @@ -44,7 +76,11 @@ impl<'de> de::Visitor<'de> for MyScalarValueVisitor {
where
E: de::Error,
{
Ok(MyScalarValue::Long(value))
if value <= i32::max_value() as i64 {
self.visit_i32(value as i32)
} else {
Ok(MyScalarValue::Long(value))
}
}

fn visit_u32<E>(self, value: u32) -> Result<MyScalarValue, E>
Expand Down

0 comments on commit 11021ea

Please sign in to comment.