Repository: graphql-rust/juniper (language: rust)

Issue:
Issues with NaiveDateTime and Query Variables
Hi there,


I have run into a little trouble making an Apollo frontend play nice with my Juniper + Diesel + Rocket backend specifically in handling NaiveDateTime values within an input object

I have a GraphQL input object 

```
use chrono::NaiveDateTime;

#[derive(juniper::GraphQLInputObject, Insertable, AsChangeset)]
#[table_name = "certificates"]
pub struct NewCertificate {
    pub name: String,
    pub version: i32,
    pub serial_no: String,
    pub pub_key: String,
    pub valid_from: NaiveDateTime,
    pub valid_till: NaiveDateTime,
    pub service_id: i32,
}
```
this is called in 
```
    fn create_certificate(context: &Context, data: NewCertificate) -> FieldResult<Certificate> {
        Ok(
            diesel::insert_into(certificates::table)
            .values(&data)
            .get_result(&context.db.get().unwrap())?
        )
    }
```

which behaves fine from graphiql as an inline declaration 
```
mutation{
  createCertificate(
    data: {
      name:"cert.domain.net",
      version:3,
      serialNo:"2F:F2:B3:F1:9B:9A:9A:07:B9:F2:C0:69:12:62:C8:F8",
      pubKey:"EE:11:57:13:16:4F:C3:1C:7C:E1:15:8D:C7:F9:4B:01:EA:47:D1:46:93:78:C7:08:B3:BD:7F:D2:91:64:BE:C9",
      validFrom:1601942400,
      validTill:1696550400,
      serviceId:12
    }
  )
  {
    id
    name
  }
}
```

but doesn't play so nice when called as a variable in a query 

```
mutation($certificate: NewCertificate!){
  createCertificate(data: $certificate){
    id
    name
    version
    serialNo
    pubKey
    validFrom
    validTill
  }
}
```
with the query variables
```
{
  "certificate": {
    "name": "cert.domain.net",
    "version": 3,
    "serialNo": "2F:F2:B3:F1:9B:9A:9A:07:B9:F2:C0:69:12:62:C8:F8",
    "pubKey": "EE:11:57:13:16:4F:C3:1C:7C:E1:15:8D:C7:F9:4B:01:EA:47:D1:46:93:78:C7:08:B3:BD:7F:D2:91:64:BE:C9",
    "validFrom": 1601942400,
    "validTill": 1696550400,
    "serviceId":12
    }
}
```

there we run into the rejection 

```
{
  "errors": [
    {
      "message": "Variable \"$certificate\" got invalid value. In field \"validFrom\": Expected \"NaiveDateTime\".",
      "locations": [
        {
          "line": 1,
          "column": 10
        }
      ]
    },
    {
      "message": "Variable \"$certificate\" got invalid value. In field \"validTill\": Expected \"NaiveDateTime\".",
      "locations": [
        {
          "line": 1,
          "column": 10
        }
      ]
    }
  ]
}
```
not sure if this is a mistake of wrong import or an issue somewhere else but I would have imagined it should be handling variables the same as inline declarations.


Cheers
Pearce




Discussion / hints:
This looks like a bug at first glance, thanks for the report!

I hope to get some time to look into this but if you want to start poking around the deserialization is defined in https://github.com/graphql-rust/juniper/blob/master/juniper/src/integrations/chrono.rs
Yup, the problem is in [this line](https://github.com/graphql-rust/juniper/blob/7578175baf9caa1d6e591dc5a29f33e9eded28fb/juniper/src/integrations/chrono.rs#L145), which is not covered by tests, as [tests use `InputValue` directly](https://github.com/graphql-rust/juniper/blob/7578175baf9caa1d6e591dc5a29f33e9eded28fb/juniper/src/integrations/chrono.rs#L240-L243).

If we look at [`f64::from_str`](https://doc.rust-lang.org/stable/std/primitive.f64.html#method.from_str) we will see, that it's not designed to parse usual int numbers, but we [do so](https://github.com/graphql-rust/juniper/blob/7578175baf9caa1d6e591dc5a29f33e9eded28fb/juniper/src/types/scalars.rs#L329-L331).

I guess, we should do something like `f64::from_str(val).or_else(|| u64::from_str(val).and_then(f64::try_from))`

Fix patch:
diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs
index 2f50eefd1..830847e43 100644
--- a/juniper/src/types/scalars.rs
+++ b/juniper/src/types/scalars.rs
@@ -325,7 +325,11 @@ where
 
     fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
         match value {
-            ScalarToken::Int(v) | ScalarToken::Float(v) => v
+            ScalarToken::Int(v) => v
+                .parse()
+                .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
+                .map(|s: i32| f64::from(s).into()),
+            ScalarToken::Float(v) => v
                 .parse()
                 .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
                 .map(|s: f64| s.into()),
@@ -334,7 +338,7 @@ where
     }
 }
 
-/// Utillity type to define read-only schemas
+/// Utility type to define read-only schemas
 ///
 /// If you instantiate `RootNode` with this as the mutation, no mutation will be
 /// generated for the schema.
@@ -457,12 +461,13 @@ impl<T> Default for EmptySubscription<T> {
 
 #[cfg(test)]
 mod tests {
-    use super::{EmptyMutation, EmptySubscription, ID};
     use crate::{
         parser::ScalarToken,
         value::{DefaultScalarValue, ParseScalarValue},
     };
 
+    use super::{EmptyMutation, EmptySubscription, ID};
+
     #[test]
     fn test_id_from_string() {
         let actual = ID::from(String::from("foo"));
@@ -505,6 +510,42 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parse_f64_from_int() {
+        for (v, expected) in &[
+            ("0", 0),
+            ("128", 128),
+            ("1601942400", 1601942400),
+            ("1696550400", 1696550400),
+            ("-1", -1),
+        ] {
+            let n = <f64 as ParseScalarValue<DefaultScalarValue>>::from_str(ScalarToken::Int(v));
+            assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err());
+
+            let n: Option<f64> = n.unwrap().into();
+            assert!(n.is_some(), "No f64 returned");
+            assert_eq!(n.unwrap(), f64::from(*expected));
+        }
+    }
+
+    #[test]
+    fn parse_f64_from_float() {
+        for (v, expected) in &[
+            ("0.", 0.),
+            ("1.2", 1.2),
+            ("1601942400.", 1601942400.),
+            ("1696550400.", 1696550400.),
+            ("-1.2", -1.2),
+        ] {
+            let n = <f64 as ParseScalarValue<DefaultScalarValue>>::from_str(ScalarToken::Float(v));
+            assert!(n.is_ok(), "A parsing error occurred: {:?}", n.unwrap_err());
+
+            let n: Option<f64> = n.unwrap().into();
+            assert!(n.is_some(), "No f64 returned");
+            assert_eq!(n.unwrap(), *expected);
+        }
+    }
+
     #[test]
     fn empty_mutation_is_send() {
         fn check_if_send<T: Send>() {}


Test patch: