Add scalars for the `time` crate's datetime types.
With the `time` feature flag enabled, `time::OffsetDateTime` and `time::PrimitiveDateTime` can be used directly as scalar values, similar to the previous `chrono::DateTime` and `chrono::NaiveDateTime` scalar implementations.
This commit is contained in:
parent
c6bb4c026c
commit
a68a9be6cf
|
@ -65,6 +65,7 @@ uuid = { version = "0.8.2", optional = true, features = ["v4", "serde"] }
|
|||
rust_decimal = { version = "1.14.3", optional = true }
|
||||
url = { version = "2.2.1", optional = true }
|
||||
smol_str = { version = "0.1.21", optional = true }
|
||||
time = { version = "0.3.5", optional = true, features = ["parsing", "formatting", "macros"] }
|
||||
|
||||
# Non-feature optional dependencies
|
||||
blocking = { version = "1.0.2", optional = true }
|
||||
|
|
|
@ -80,6 +80,7 @@ This crate offers the following features, all of which are not activated by defa
|
|||
- `cbor`: Support for [serde_cbor](https://crates.io/crates/serde_cbor).
|
||||
- `smol_str`: Integrate with the [`smol_str` crate](https://crates.io/crates/smol_str).
|
||||
- `hashbrown`: Integrate with the [`hashbrown` crate](https://github.com/rust-lang/hashbrown).
|
||||
- `time`: Integrate with the [`time` crate](https://github.com/time-rs/time).
|
||||
|
||||
## Apollo Studio
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
//! - `cbor`: Support for [serde_cbor](https://crates.io/crates/serde_cbor).
|
||||
//! - `smol_str`: Integrate with the [`smol_str` crate](https://crates.io/crates/smol_str).
|
||||
//! - `hashbrown`: Integrate with the [`hashbrown` crate](https://github.com/rust-lang/hashbrown).
|
||||
//! - `time`: Integrate with the [`time` crate](https://github.com/time-rs/time).
|
||||
//!
|
||||
//! ## Integrations
|
||||
//!
|
||||
|
|
|
@ -28,6 +28,10 @@ mod naive_time;
|
|||
mod secrecy;
|
||||
#[cfg(feature = "smol_str")]
|
||||
mod smol_str;
|
||||
#[cfg(feature = "time")]
|
||||
mod time_offset_date_time;
|
||||
#[cfg(feature = "time")]
|
||||
mod time_primitive_date_time;
|
||||
#[cfg(feature = "url")]
|
||||
mod url;
|
||||
#[cfg(feature = "uuid")]
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
|
||||
|
||||
/// A datetime with timezone offset.
|
||||
///
|
||||
/// The input is a string in RFC3339 format, e.g. "2022-01-12T04:00:19.12345Z"
|
||||
/// or "2022-01-12T04:00:19+03:00". The output is also a string in RFC3339
|
||||
/// format, but it is always normalized to the UTC (Z) offset, e.g.
|
||||
/// "2022-01-12T04:00:19.12345Z".
|
||||
#[Scalar(
|
||||
internal,
|
||||
name = "DateTime",
|
||||
specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339"
|
||||
)]
|
||||
impl ScalarType for OffsetDateTime {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match &value {
|
||||
Value::String(s) => Ok(Self::parse(s, &Rfc3339)?),
|
||||
_ => Err(InputValueError::expected_type(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(
|
||||
self.to_offset(UtcOffset::UTC)
|
||||
.format(&Rfc3339)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `OffsetDateTime`: {}", e)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ScalarType, Value};
|
||||
use time::{macros::datetime, OffsetDateTime};
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time_to_value() {
|
||||
let cases = [
|
||||
(
|
||||
datetime!(2022-01-12 07:30:19.12345 +3:30),
|
||||
"2022-01-12T04:00:19.12345Z",
|
||||
),
|
||||
(datetime!(2022-01-12 07:30:19-0), "2022-01-12T07:30:19Z"),
|
||||
];
|
||||
for (value, expected) in cases {
|
||||
let value = value.to_value();
|
||||
|
||||
if let Value::String(s) = value {
|
||||
assert_eq!(s, expected);
|
||||
} else {
|
||||
panic!(
|
||||
"Unexpected Value type when formatting OffsetDateTime: {:?}",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_date_time_parse() {
|
||||
let cases = [
|
||||
(
|
||||
"2022-01-12T04:00:19.12345Z",
|
||||
datetime!(2022-01-12 07:30:19.12345 +3:30),
|
||||
),
|
||||
(
|
||||
"2022-01-12T23:22:19.12345-00:00",
|
||||
datetime!(2022-01-12 23:22:19.12345-0),
|
||||
),
|
||||
];
|
||||
for (value, expected) in cases {
|
||||
let value = Value::String(value.to_string());
|
||||
let parsed = <OffsetDateTime as ScalarType>::parse(value).unwrap();
|
||||
assert_eq!(parsed, expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
|
||||
use time::{format_description::FormatItem, macros::format_description, PrimitiveDateTime};
|
||||
|
||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]");
|
||||
|
||||
/// A local datetime without timezone offset.
|
||||
///
|
||||
/// The input/output is a string in ISO 8601 format without timezone, including
|
||||
/// subseconds. E.g. "2022-01-12T07:30:19.12345".
|
||||
#[Scalar(internal, name = "LocalDateTime")]
|
||||
impl ScalarType for PrimitiveDateTime {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match &value {
|
||||
Value::String(s) => Ok(Self::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)?),
|
||||
_ => Err(InputValueError::expected_type(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(
|
||||
self.format(&PRIMITIVE_DATE_TIME_FORMAT)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `PrimitiveDateTime`: {}", e)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ScalarType, Value};
|
||||
use time::{macros::datetime, PrimitiveDateTime};
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_to_value() {
|
||||
let cases = [
|
||||
(
|
||||
datetime!(2022-01-12 07:30:19.12345),
|
||||
"2022-01-12T07:30:19.12345",
|
||||
),
|
||||
(datetime!(2022-01-12 07:30:19), "2022-01-12T07:30:19.0"),
|
||||
];
|
||||
for (value, expected) in cases {
|
||||
let value = value.to_value();
|
||||
|
||||
if let Value::String(s) = value {
|
||||
assert_eq!(s, expected);
|
||||
} else {
|
||||
panic!(
|
||||
"Unexpected Value type when formatting PrimitiveDateTime: {:?}",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitive_date_time_parse() {
|
||||
let cases = [
|
||||
(
|
||||
"2022-01-12T07:30:19.12345",
|
||||
datetime!(2022-01-12 07:30:19.12345),
|
||||
),
|
||||
("2022-01-12T07:30:19.0", datetime!(2022-01-12 07:30:19)),
|
||||
];
|
||||
for (value, expected) in cases {
|
||||
let value = Value::String(value.to_string());
|
||||
let parsed = <PrimitiveDateTime as ScalarType>::parse(value).unwrap();
|
||||
assert_eq!(parsed, expected);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue