#![allow(clippy::needless_lifetimes)]
use chrono::prelude::*;
use crate::{
parser::{ParseError, ScalarToken, Token},
value::{ParseScalarResult, ParseScalarValue},
Value,
};
#[doc(hidden)]
pub static RFC3339_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f%:z";
#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")]
impl<S> GraphQLScalar for DateTime<FixedOffset>
where
S: ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.to_rfc3339())
}
fn from_input_value(v: &InputValue) -> Option<DateTime<FixedOffset>> {
v.as_string_value()
.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
if let ScalarToken::String(value) = value {
Ok(S::from(value.to_owned()))
} else {
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
}
}
}
#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")]
impl<S> GraphQLScalar for DateTime<Utc>
where
S: ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.to_rfc3339())
}
fn from_input_value(v: &InputValue) -> Option<DateTime<Utc>> {
v.as_string_value()
.and_then(|s| (s.parse::<DateTime<Utc>>().ok()))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
if let ScalarToken::String(value) = value {
Ok(S::from(value.to_owned()))
} else {
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
}
}
}
#[crate::graphql_scalar(description = "NaiveDate")]
impl<S> GraphQLScalar for NaiveDate
where
S: ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.format("%Y-%m-%d").to_string())
}
fn from_input_value(v: &InputValue) -> Option<NaiveDate> {
v.as_string_value()
.and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok())
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
if let ScalarToken::String(value) = value {
Ok(S::from(value.to_owned()))
} else {
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
}
}
}
#[cfg(feature = "scalar-naivetime")]
#[crate::graphql_scalar(description = "NaiveTime")]
impl<S> GraphQLScalar for NaiveTime
where
S: ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.format("%H:%M:%S").to_string())
}
fn from_input_value(v: &InputValue) -> Option<NaiveTime> {
v.as_string_value()
.and_then(|s| NaiveTime::parse_from_str(s, "%H:%M:%S").ok())
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
if let ScalarToken::String(value) = value {
Ok(S::from(value.to_owned()))
} else {
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
}
}
}
#[crate::graphql_scalar(description = "NaiveDateTime")]
impl<S> GraphQLScalar for NaiveDateTime
where
S: ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.timestamp() as f64)
}
fn from_input_value(v: &InputValue) -> Option<NaiveDateTime> {
v.as_float_value()
.and_then(|f| NaiveDateTime::from_timestamp_opt(f as i64, 0))
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
<f64 as ParseScalarValue<S>>::from_str(value)
}
}
#[cfg(test)]
mod test {
use crate::{value::DefaultScalarValue, InputValue};
use chrono::prelude::*;
fn datetime_fixedoffset_test(raw: &'static str) {
let input: crate::InputValue<DefaultScalarValue> = InputValue::scalar(raw.to_string());
let parsed: DateTime<FixedOffset> =
crate::FromInputValue::from_input_value(&input).unwrap();
let expected = DateTime::parse_from_rfc3339(raw).unwrap();
assert_eq!(parsed, expected);
}
#[test]
fn datetime_fixedoffset_from_input_value() {
datetime_fixedoffset_test("2014-11-28T21:00:09+09:00");
}
#[test]
fn datetime_fixedoffset_from_input_value_with_z_timezone() {
datetime_fixedoffset_test("2014-11-28T21:00:09Z");
}
#[test]
fn datetime_fixedoffset_from_input_value_with_fractional_seconds() {
datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00");
}
fn datetime_utc_test(raw: &'static str) {
let input: crate::InputValue<DefaultScalarValue> = InputValue::scalar(raw.to_string());
let parsed: DateTime<Utc> = crate::FromInputValue::from_input_value(&input).unwrap();
let expected = DateTime::parse_from_rfc3339(raw)
.unwrap()
.with_timezone(&Utc);
assert_eq!(parsed, expected);
}
#[test]
fn datetime_utc_from_input_value() {
datetime_utc_test("2014-11-28T21:00:09+09:00")
}
#[test]
fn datetime_utc_from_input_value_with_z_timezone() {
datetime_utc_test("2014-11-28T21:00:09Z")
}
#[test]
fn datetime_utc_from_input_value_with_fractional_seconds() {
datetime_utc_test("2014-11-28T21:00:09.005+09:00");
}
#[test]
fn naivedate_from_input_value() {
let input: crate::InputValue<DefaultScalarValue> =
InputValue::scalar("1996-12-19".to_string());
let y = 1996;
let m = 12;
let d = 19;
let parsed: NaiveDate = crate::FromInputValue::from_input_value(&input).unwrap();
let expected = NaiveDate::from_ymd(y, m, d);
assert_eq!(parsed, expected);
assert_eq!(parsed.year(), y);
assert_eq!(parsed.month(), m);
assert_eq!(parsed.day(), d);
}
#[test]
#[cfg(feature = "scalar-naivetime")]
fn naivetime_from_input_value() {
let input: crate::InputValue<DefaultScalarValue>;
input = InputValue::scalar("21:12:19".to_string());
let [h, m, s] = [21, 12, 19];
let parsed: NaiveTime = crate::FromInputValue::from_input_value(&input).unwrap();
let expected = NaiveTime::from_hms(h, m, s);
assert_eq!(parsed, expected);
assert_eq!(parsed.hour(), h);
assert_eq!(parsed.minute(), m);
assert_eq!(parsed.second(), s);
}
#[test]
fn naivedatetime_from_input_value() {
let raw = 1_000_000_000_f64;
let input: InputValue<DefaultScalarValue> = InputValue::scalar(raw);
let parsed: NaiveDateTime = crate::FromInputValue::from_input_value(&input).unwrap();
let expected = NaiveDateTime::from_timestamp_opt(raw as i64, 0).unwrap();
assert_eq!(parsed, expected);
assert_eq!(raw, expected.timestamp() as f64);
}
}
#[cfg(test)]
mod integration_test {
use chrono::{prelude::*, Utc};
use crate::{
executor::Variables,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
value::Value,
};
#[tokio::test]
async fn test_serialization() {
struct Root;
#[crate::graphql_object]
#[cfg(feature = "scalar-naivetime")]
impl Root {
fn exampleNaiveDate() -> NaiveDate {
NaiveDate::from_ymd(2015, 3, 14)
}
fn exampleNaiveDateTime() -> NaiveDateTime {
NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
}
fn exampleNaiveTime() -> NaiveTime {
NaiveTime::from_hms(16, 7, 8)
}
fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
}
fn exampleDateTimeUtc() -> DateTime<Utc> {
Utc.timestamp(61, 0)
}
}
#[crate::graphql_object]
#[cfg(not(feature = "scalar-naivetime"))]
impl Root {
fn exampleNaiveDate() -> NaiveDate {
NaiveDate::from_ymd(2015, 3, 14)
}
fn exampleNaiveDateTime() -> NaiveDateTime {
NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
}
fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
}
fn exampleDateTimeUtc() -> DateTime<Utc> {
Utc.timestamp(61, 0)
}
}
#[cfg(feature = "scalar-naivetime")]
let doc = r#"
{
exampleNaiveDate,
exampleNaiveDateTime,
exampleNaiveTime,
exampleDateTimeFixedOffset,
exampleDateTimeUtc,
}
"#;
#[cfg(not(feature = "scalar-naivetime"))]
let doc = r#"
{
exampleNaiveDate,
exampleNaiveDateTime,
exampleDateTimeFixedOffset,
exampleDateTimeUtc,
}
"#;
let schema = RootNode::new(
Root,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &())
.await
.expect("Execution failed");
assert_eq!(errs, []);
assert_eq!(
result,
Value::object(
vec![
("exampleNaiveDate", Value::scalar("2015-03-14")),
("exampleNaiveDateTime", Value::scalar(1_467_969_011.0)),
#[cfg(feature = "scalar-naivetime")]
("exampleNaiveTime", Value::scalar("16:07:08")),
(
"exampleDateTimeFixedOffset",
Value::scalar("1996-12-19T16:39:57-08:00"),
),
(
"exampleDateTimeUtc",
Value::scalar("1970-01-01T00:01:01+00:00"),
),
]
.into_iter()
.collect()
)
);
}
}