- represent `jiff::civil::Date` as `LocalDate` GraphQL scalar - represent `jiff::civil::Time` as `LocalTime` GraphQL scalar - represent `jiff::civil::DateTime` as `LocalDateTime` GraphQL scalar - represent `jiff::Timestamp` as `DateTime` GraphQL scalar - represent `jiff::Span` as `Duration` GraphQL scalar
This commit is contained in:
parent
975da450fe
commit
2756260000
9 changed files with 763 additions and 0 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -107,6 +107,7 @@ jobs:
|
|||
- { feature: chrono-clock, crate: juniper }
|
||||
- { feature: chrono-tz, crate: juniper }
|
||||
- { feature: expose-test-schema, crate: juniper }
|
||||
- { feature: jiff, crate: juniper }
|
||||
- { feature: rust_decimal, crate: juniper }
|
||||
- { feature: schema-language, crate: juniper }
|
||||
- { feature: time, crate: juniper }
|
||||
|
|
|
@ -73,6 +73,7 @@ your Schemas automatically.
|
|||
- [url][url]
|
||||
- [chrono][chrono]
|
||||
- [chrono-tz][chrono-tz]
|
||||
- [jiff][jiff]
|
||||
- [time][time]
|
||||
- [bson][bson]
|
||||
|
||||
|
@ -119,6 +120,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
|||
[url]: https://crates.io/crates/url
|
||||
[chrono]: https://crates.io/crates/chrono
|
||||
[chrono-tz]: https://crates.io/crates/chrono-tz
|
||||
[jiff]: https://crates.io/crates/jiff
|
||||
[time]: https://crates.io/crates/time
|
||||
[bson]: https://crates.io/crates/bson
|
||||
[juniper-from-schema]: https://github.com/davidpdrsn/juniper-from-schema
|
||||
|
|
|
@ -31,6 +31,7 @@ Introduction
|
|||
- [`bigdecimal`]
|
||||
- [`bson`]
|
||||
- [`chrono`], [`chrono-tz`]
|
||||
- [`jiff`]
|
||||
- [`rust_decimal`]
|
||||
- [`time`]
|
||||
- [`url`]
|
||||
|
@ -63,6 +64,7 @@ Introduction
|
|||
[`bson`]: https://docs.rs/bson
|
||||
[`chrono`]: https://docs.rs/chrono
|
||||
[`chrono-tz`]: https://docs.rs/chrono-tz
|
||||
[`jiff`]: https://docs.rs/jiff
|
||||
[`juniper`]: https://docs.rs/juniper
|
||||
[`juniper_actix`]: https://docs.rs/juniper_actix
|
||||
[`juniper_axum`]: https://docs.rs/juniper_axum
|
||||
|
|
|
@ -396,6 +396,11 @@ mod date_scalar {
|
|||
| [`chrono::DateTime`] | [`DateTime`] | [`chrono`] |
|
||||
| [`chrono_tz::Tz`] | `TimeZone` | [`chrono-tz`] |
|
||||
| [`Decimal`] | `Decimal` | [`rust_decimal`] |
|
||||
| [`jiff::civil::Date`] | [`LocalDate`] | [`jiff`] |
|
||||
| [`jiff::civil::Time`] | [`LocalTime`] | [`jiff`] |
|
||||
| [`jiff::civil::DateTime`] | `LocalDateTime` | [`jiff`] |
|
||||
| [`jiff::Timestamp`] | [`DateTime`] | [`jiff`] |
|
||||
| [`jiff::Span`] | [`Duration`] | [`jiff`] |
|
||||
| [`time::Date`] | [`Date`] | [`time`] |
|
||||
| [`time::Time`] | [`LocalTime`] | [`time`] |
|
||||
| [`time::PrimitiveDateTime`] | `LocalDateTime` | [`time`] |
|
||||
|
@ -422,7 +427,15 @@ mod date_scalar {
|
|||
[`Date`]: https://graphql-scalars.dev/docs/scalars/date
|
||||
[`DateTime`]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
[`Decimal`]: https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html
|
||||
[`Duration`]: https://graphql-scalars.dev/docs/scalars/duration
|
||||
[`ID`]: https://spec.graphql.org/October2021#sec-ID
|
||||
[`jiff`]: https://docs.rs/jiff
|
||||
[`jiff::civil::Date`]: https://docs.rs/jiff/latest/jiff/civil/struct.Date.html
|
||||
[`jiff::civil::DateTime`]: https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html
|
||||
[`jiff::civil::Time`]: https://docs.rs/jiff/latest/jiff/civil/struct.Time.html
|
||||
[`jiff::Span`]: https://docs.rs/jiff/latest/jiff/struct.Span.html
|
||||
[`jiff::Timestamp`]: https://docs.rs/jiff/latest/jiff/struct.Timestamp.html
|
||||
[`LocalDate`]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
[`LocalTime`]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
[`rust_decimal`]: https://docs.rs/rust_decimal
|
||||
[`ScalarValue`]: https://docs.rs/juniper/0.16.1/juniper/trait.ScalarValue.html
|
||||
|
|
|
@ -15,11 +15,16 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
|||
- Upgraded [`chrono-tz` crate] integration to [0.9 version](https://github.com/chronotope/chrono-tz/releases/tag/v0.9.0). ([#1252])
|
||||
- Bumped up [MSRV] to 1.75. ([#1272])
|
||||
|
||||
### Added
|
||||
|
||||
- [`jiff` crate] integration behind `jiff` [Cargo feature]. ([#1271])
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated [GraphiQL] to [3.5.0 version](https://github.com/graphql/graphiql/blob/graphiql%403.5.0/packages/graphiql/CHANGELOG.md#350). ([#1274])
|
||||
|
||||
[#1252]: /../../pull/1252
|
||||
[#1271]: /../../pull/1271
|
||||
[#1272]: /../../pull/1272
|
||||
[#1274]: /../../pull/1274
|
||||
|
||||
|
@ -222,6 +227,7 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md).
|
|||
[`bson` crate]: https://docs.rs/bson
|
||||
[`chrono` crate]: https://docs.rs/chrono
|
||||
[`chrono-tz` crate]: https://docs.rs/chrono-tz
|
||||
[`jiff` crate]: https://docs.rs/jiff
|
||||
[`time` crate]: https://docs.rs/time
|
||||
[Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html
|
||||
[`graphql-transport-ws` GraphQL over WebSocket Protocol]: https://github.com/enisdenjo/graphql-ws/v5.14.0/PROTOCOL.md
|
||||
|
|
|
@ -33,6 +33,7 @@ chrono = ["dep:chrono"]
|
|||
chrono-clock = ["chrono", "chrono/clock"]
|
||||
chrono-tz = ["dep:chrono-tz", "dep:regex"]
|
||||
expose-test-schema = ["dep:anyhow", "dep:serde_json"]
|
||||
jiff = ["dep:jiff"]
|
||||
js = ["chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js"]
|
||||
rust_decimal = ["dep:rust_decimal"]
|
||||
schema-language = ["dep:graphql-parser", "dep:void"]
|
||||
|
@ -52,6 +53,7 @@ fnv = "1.0.5"
|
|||
futures = { version = "0.3.22", features = ["alloc"], default-features = false }
|
||||
graphql-parser = { version = "0.4", optional = true }
|
||||
indexmap = { version = "2.0", features = ["serde"] }
|
||||
jiff = { version = "0.1.5", features = ["alloc"], default-features = false, optional = true }
|
||||
juniper_codegen = { version = "0.16.0", path = "../juniper_codegen" }
|
||||
rust_decimal = { version = "1.20", default-features = false, optional = true }
|
||||
ryu = { version = "1.0", optional = true }
|
||||
|
|
|
@ -48,6 +48,7 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil
|
|||
- [`bigdecimal`]
|
||||
- [`bson`]
|
||||
- [`chrono`], [`chrono-tz`]
|
||||
- [`jiff`]
|
||||
- [`rust_decimal`]
|
||||
- [`time`]
|
||||
- [`url`]
|
||||
|
@ -85,6 +86,7 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
|
|||
[`bson`]: https://docs.rs/bson
|
||||
[`chrono`]: https://docs.rs/chrono
|
||||
[`chrono-tz`]: https://docs.rs/chrono-tz
|
||||
[`jiff`]: https://docs.rs/jiff
|
||||
[`juniper_actix`]: https://docs.rs/juniper_actix
|
||||
[`juniper_axum`]: https://docs.rs/juniper_axum
|
||||
[`juniper_hyper`]: https://docs.rs/juniper_hyper
|
||||
|
|
733
juniper/src/integrations/jiff.rs
Normal file
733
juniper/src/integrations/jiff.rs
Normal file
|
@ -0,0 +1,733 @@
|
|||
//! GraphQL support for [`jiff`] crate types.
|
||||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |---------------------|-----------------------|-----------------------|
|
||||
//! | [`civil::Date`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
|
||||
//! | [`civil::Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
|
||||
//! | [`civil::DateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
|
||||
//! | [`Timestamp`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//! | [`Span`] | [ISO 8601] duration | [`Duration`][s5] |
|
||||
//!
|
||||
//! # Unsupported types
|
||||
//!
|
||||
//! [`Zoned`] is not supported because the GraphQL scalar [`DateTime`][s4] only supports time zone
|
||||
//! offsets but no IANA time zone names (as in `2024-08-10T23:14:00-04:00[America/New_York]`, cf.
|
||||
//! [RFC 9557]). Serializing such values would incur a loss of information with unexpected and
|
||||
//! subtle consequences (a fixed offset would only _seem_ to work in most cases).
|
||||
//!
|
||||
//! [`civil::Date`]: jiff::civil::Date
|
||||
//! [`civil::DateTime`]: jiff::civil::DateTime
|
||||
//! [`civil::Time`]: jiff::civil::Time
|
||||
//! [`Span`]: jiff::Span
|
||||
//! [`Timestamp`]: jiff::Timestamp
|
||||
//! [`Zoned`]: jiff::Zoned
|
||||
//! [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations
|
||||
//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
//! [RFC 9557]: https://datatracker.ietf.org/doc/html/rfc9557#section-4.1
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
//! [s2]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
//! [s3]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
//! [s4]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
//! [s5]: https://graphql-scalars.dev/docs/scalars/duration
|
||||
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
/// Representation of a civil date in the Gregorian calendar.
|
||||
///
|
||||
/// Corresponds to a triple of year, month and day. Every value is guaranteed to be a valid
|
||||
/// Gregorian calendar date. For example, both `2023-02-29` and `2023-11-31` are invalid and cannot
|
||||
/// be represented.
|
||||
///
|
||||
/// [`LocalDate` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`jiff::civil::Date`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
/// [2]: https://docs.rs/jiff/latest/jiff/civil/struct.Date.html
|
||||
#[graphql_scalar(
|
||||
with = local_date,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date",
|
||||
)]
|
||||
pub type LocalDate = jiff::civil::Date;
|
||||
|
||||
mod local_date {
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`LocalDate` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
const FORMAT: &str = "%Y-%m-%d";
|
||||
|
||||
pub(super) fn to_output<S>(v: &LocalDate) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(v.strftime(FORMAT).to_string())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<LocalDate, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| {
|
||||
LocalDate::strptime(FORMAT, s).map_err(|e| format!("Invalid `LocalDate`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a civil "wall clock" time.
|
||||
///
|
||||
/// Conceptually, corresponds to the typical hours and minutes that you might see on a clock. This
|
||||
/// type also contains the second and fractional subsecond (to nanosecond precision) associated with
|
||||
/// a time.
|
||||
///
|
||||
/// [`LocalTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`jiff::civil::Time`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
/// [2]: https://docs.rs/jiff/latest/jiff/civil/struct.Time.html
|
||||
#[graphql_scalar(
|
||||
with = local_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time",
|
||||
)]
|
||||
pub type LocalTime = jiff::civil::Time;
|
||||
|
||||
mod local_time {
|
||||
use super::*;
|
||||
|
||||
/// Full format of a [`LocalTime` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
const FORMAT: &str = "%H:%M:%S%.3f";
|
||||
|
||||
/// Format of a [`LocalTime` scalar][1] without milliseconds.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
const FORMAT_NO_MILLIS: &str = "%H:%M:%S";
|
||||
|
||||
/// Format of a [`LocalTime` scalar][1] without seconds.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
const FORMAT_NO_SECS: &str = "%H:%M";
|
||||
|
||||
pub(super) fn to_output<S>(v: &LocalTime) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(
|
||||
if v.subsec_nanosecond() == 0 {
|
||||
v.strftime(FORMAT_NO_MILLIS)
|
||||
} else {
|
||||
v.strftime(FORMAT)
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<LocalTime, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| {
|
||||
// First, try to parse the most used format.
|
||||
// At the end, try to parse the full format for the parsing
|
||||
// error to be most informative.
|
||||
LocalTime::strptime(FORMAT_NO_MILLIS, s)
|
||||
.or_else(|_| LocalTime::strptime(FORMAT_NO_SECS, s))
|
||||
.or_else(|_| LocalTime::strptime(FORMAT, s))
|
||||
.map_err(|e| format!("Invalid `LocalTime`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a civil datetime in the Gregorian calendar.
|
||||
///
|
||||
/// Corresponds to a pair of a `LocalDate` and a `LocalTime`. That is, a datetime contains a year,
|
||||
/// month, day, hour, minute, second and the fractional number of nanoseconds.
|
||||
///
|
||||
/// Value is guaranteed to contain a valid date and time. For example, neither `2023-02-29T00:00:00`
|
||||
/// nor `2015-06-30T23:59:60` are valid.
|
||||
///
|
||||
/// [`LocalDateTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`jiff::civil::DateTime`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
/// [2]: https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html
|
||||
#[graphql_scalar(
|
||||
with = local_date_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time",
|
||||
)]
|
||||
pub type LocalDateTime = jiff::civil::DateTime;
|
||||
|
||||
mod local_date_time {
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`LocalDateTime` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
const FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||
|
||||
pub(super) fn to_output<S>(v: &LocalDateTime) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(v.strftime(FORMAT).to_string())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<LocalDateTime, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| {
|
||||
LocalDateTime::strptime(FORMAT, s)
|
||||
.map_err(|e| format!("Invalid `LocalDateTime`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Instant in time represented as the number of nanoseconds since the Unix epoch.
|
||||
///
|
||||
/// Always in UTC.
|
||||
///
|
||||
/// [`DateTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`jiff::Timestamp`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
/// [2]: https://docs.rs/jiff/latest/jiff/struct.Timestamp.html
|
||||
#[graphql_scalar(
|
||||
with = date_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
|
||||
)]
|
||||
pub type DateTime = jiff::Timestamp;
|
||||
|
||||
mod date_time {
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`DateTime` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ";
|
||||
|
||||
pub(super) fn to_output<S>(v: &DateTime) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(v.strftime(FORMAT).to_string())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<DateTime, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Span of time represented via a mixture of calendar and clock units.
|
||||
///
|
||||
/// Represents a duration of time in units of years, months, weeks, days, hours, minutes, seconds,
|
||||
/// milliseconds, microseconds and nanoseconds.
|
||||
///
|
||||
/// [`Duration` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`jiff::Span`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/duration
|
||||
/// [2]: https://docs.rs/jiff/latest/jiff/struct.Span.html
|
||||
#[graphql_scalar(
|
||||
with = duration,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/duration",
|
||||
)]
|
||||
pub type Duration = jiff::Span;
|
||||
|
||||
mod duration {
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S>(v: &Duration) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(v.to_string())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<Duration, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod local_date_test {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::LocalDate;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
("1996-12-19", LocalDate::constant(1996, 12, 19)),
|
||||
("1564-01-30", LocalDate::constant(1564, 01, 30)),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_input() {
|
||||
for input in [
|
||||
graphql_input_value!("1996-13-19"),
|
||||
graphql_input_value!("1564-01-61"),
|
||||
graphql_input_value!("2021-11-31"),
|
||||
graphql_input_value!("11-31"),
|
||||
graphql_input_value!("2021-11"),
|
||||
graphql_input_value!("2021"),
|
||||
graphql_input_value!("31"),
|
||||
graphql_input_value!("i'm not even a date"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
graphql_input_value!(null),
|
||||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
LocalDate::constant(1996, 12, 19),
|
||||
graphql_input_value!("1996-12-19"),
|
||||
),
|
||||
(
|
||||
LocalDate::constant(1564, 01, 30),
|
||||
graphql_input_value!("1564-01-30"),
|
||||
),
|
||||
(
|
||||
LocalDate::constant(2020, 01, 01),
|
||||
graphql_input_value!("2020-01-01"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod local_time_test {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::LocalTime;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
("14:23:43", LocalTime::constant(14, 23, 43, 000_000_000)),
|
||||
("14:00:00", LocalTime::constant(14, 00, 00, 000_000_000)),
|
||||
("14:00", LocalTime::constant(14, 00, 00, 000_000_000)),
|
||||
("14:32", LocalTime::constant(14, 32, 00, 000_000_000)),
|
||||
("14:00:00.000", LocalTime::constant(14, 00, 00, 000_000_000)),
|
||||
("14:23:43.345", LocalTime::constant(14, 23, 43, 345_000_000)),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = LocalTime::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_input() {
|
||||
for input in [
|
||||
graphql_input_value!("12"),
|
||||
graphql_input_value!("12:"),
|
||||
graphql_input_value!("56:34:22"),
|
||||
graphql_input_value!("23:78:43"),
|
||||
graphql_input_value!("23:78:"),
|
||||
graphql_input_value!("23:18:99"),
|
||||
graphql_input_value!("23:18:22."),
|
||||
graphql_input_value!("22.03"),
|
||||
graphql_input_value!("24:00"),
|
||||
graphql_input_value!("24:00:00"),
|
||||
graphql_input_value!("24:00:00.000"),
|
||||
graphql_input_value!("i'm not even a time"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
graphql_input_value!(null),
|
||||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = LocalTime::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
LocalTime::constant(1, 2, 3, 4_005_000),
|
||||
graphql_input_value!("01:02:03.004"),
|
||||
),
|
||||
(
|
||||
LocalTime::constant(0, 0, 0, 0),
|
||||
graphql_input_value!("00:00:00"),
|
||||
),
|
||||
(
|
||||
LocalTime::constant(12, 0, 0, 0),
|
||||
graphql_input_value!("12:00:00"),
|
||||
),
|
||||
(
|
||||
LocalTime::constant(1, 2, 3, 0),
|
||||
graphql_input_value!("01:02:03"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod local_date_time_test {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::LocalDateTime;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
(
|
||||
"1996-12-19 14:23:43",
|
||||
LocalDateTime::constant(1996, 12, 19, 14, 23, 43, 0),
|
||||
),
|
||||
(
|
||||
"1564-01-30 14:00:00",
|
||||
LocalDateTime::constant(1564, 1, 30, 14, 00, 00, 0),
|
||||
),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = LocalDateTime::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_input() {
|
||||
for input in [
|
||||
graphql_input_value!("12"),
|
||||
graphql_input_value!("12:"),
|
||||
graphql_input_value!("56:34:22"),
|
||||
graphql_input_value!("56:34:22.000"),
|
||||
graphql_input_value!("1996-12-19T14:23:43"),
|
||||
graphql_input_value!("1996-12-19 14:23:43Z"),
|
||||
graphql_input_value!("1996-12-19 14:23:43.543"),
|
||||
graphql_input_value!("1996-12-19 14:23"),
|
||||
graphql_input_value!("1996-12-19 14:23:"),
|
||||
graphql_input_value!("1996-12-19 23:78:43"),
|
||||
graphql_input_value!("1996-12-19 23:18:99"),
|
||||
graphql_input_value!("1996-12-19 24:00:00"),
|
||||
graphql_input_value!("1996-12-19 99:02:13"),
|
||||
graphql_input_value!("i'm not even a datetime"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
graphql_input_value!(null),
|
||||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = LocalDateTime::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
LocalDateTime::constant(1996, 12, 19, 0, 0, 0, 0),
|
||||
graphql_input_value!("1996-12-19 00:00:00"),
|
||||
),
|
||||
(
|
||||
LocalDateTime::constant(1564, 1, 30, 14, 0, 0, 0),
|
||||
graphql_input_value!("1564-01-30 14:00:00"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod date_time_test {
|
||||
use jiff::{civil, tz::TimeZone};
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::DateTime;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
(
|
||||
"2014-11-28T21:00:09+09:00",
|
||||
civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09Z",
|
||||
civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
(
|
||||
"2014-11-28 21:00:09z",
|
||||
civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09+00:00",
|
||||
civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09.05+09:00",
|
||||
civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 50_000_000)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
(
|
||||
"2014-11-28 21:00:09.05+09:00",
|
||||
civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 50_000_000)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = DateTime::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_input() {
|
||||
for input in [
|
||||
graphql_input_value!("12"),
|
||||
graphql_input_value!("12:"),
|
||||
graphql_input_value!("56:34:22"),
|
||||
graphql_input_value!("56:34:22.000"),
|
||||
graphql_input_value!("1996-12-1914:23:43"),
|
||||
graphql_input_value!("1996-12-19Q14:23:43Z"),
|
||||
graphql_input_value!("1996-12-19T14:23:43"),
|
||||
graphql_input_value!("1996-12-19T14:23:43ZZ"),
|
||||
graphql_input_value!("1996-12-19T14:23:43.543"),
|
||||
graphql_input_value!("1996-12-19T14:23"),
|
||||
graphql_input_value!("1996-12-19T14:23:1"),
|
||||
graphql_input_value!("1996-12-19T14:23:"),
|
||||
graphql_input_value!("1996-12-19T23:78:43Z"),
|
||||
graphql_input_value!("1996-12-19T23:18:99Z"),
|
||||
graphql_input_value!("1996-12-19T24:00:00Z"),
|
||||
graphql_input_value!("1996-12-19T99:02:13Z"),
|
||||
graphql_input_value!("1996-12-19T99:02:13Z"),
|
||||
graphql_input_value!("1996-12-19T12:02:13+4444444"),
|
||||
graphql_input_value!("i'm not even a datetime"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
graphql_input_value!(null),
|
||||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = DateTime::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
civil::DateTime::constant(1996, 12, 19, 0, 0, 0, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
graphql_input_value!("1996-12-19T00:00:00Z"),
|
||||
),
|
||||
(
|
||||
civil::DateTime::constant(1564, 1, 30, 5, 0, 0, 123_000_000)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
graphql_input_value!("1564-01-30T05:00:00.123Z"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod duration_test {
|
||||
use jiff::ToSpan;
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::Duration;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
("P5dT8h1m", 5.days().hours(8).minutes(1)),
|
||||
("-P5d", (-5).days()),
|
||||
("P2M10DT2H30M", 2.months().days(10).hours(2).minutes(30)),
|
||||
("P40D", 40.days()),
|
||||
("P1y1d", 1.year().days(1)),
|
||||
("P3dT4h59m", 3.days().hours(4).minutes(59)),
|
||||
("PT2H30M", 2.hours().minutes(30)),
|
||||
("P1m", 1.month()),
|
||||
("P1w", 1.week()),
|
||||
("P1w4d", 1.week().days(4)),
|
||||
("PT1m", 1.minute()),
|
||||
("PT0.0021s", 2.milliseconds().microseconds(100)),
|
||||
("PT0s", 0.seconds()),
|
||||
("P0d", 0.seconds()),
|
||||
(
|
||||
"P1y1m1dT1h1m1.1s",
|
||||
1.year()
|
||||
.months(1)
|
||||
.days(1)
|
||||
.hours(1)
|
||||
.minutes(1)
|
||||
.seconds(1)
|
||||
.milliseconds(100),
|
||||
),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = Duration::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_input() {
|
||||
for input in [
|
||||
graphql_input_value!("12"),
|
||||
graphql_input_value!("12S"),
|
||||
graphql_input_value!("P0"),
|
||||
graphql_input_value!("PT"),
|
||||
graphql_input_value!("PTS"),
|
||||
graphql_input_value!("56:34:22"),
|
||||
graphql_input_value!("1996-12-19"),
|
||||
graphql_input_value!("1996-12-19T14:23:43"),
|
||||
graphql_input_value!("1996-12-19T14:23:43Z"),
|
||||
graphql_input_value!("i'm not even a duration"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
graphql_input_value!(null),
|
||||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = Duration::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
1.year()
|
||||
.months(1)
|
||||
.days(1)
|
||||
.hours(1)
|
||||
.minutes(1)
|
||||
.seconds(1)
|
||||
.milliseconds(100),
|
||||
graphql_input_value!("P1y1m1dT1h1m1.1s"),
|
||||
),
|
||||
((-5).days(), graphql_input_value!("-P5d")),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ pub mod bson;
|
|||
pub mod chrono;
|
||||
#[cfg(feature = "chrono-tz")]
|
||||
pub mod chrono_tz;
|
||||
#[cfg(feature = "jiff")]
|
||||
pub mod jiff;
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
pub mod rust_decimal;
|
||||
#[doc(hidden)]
|
||||
|
|
Loading…
Reference in a new issue