Correct GraphQL scalars compliance for third-party crates (#1275)
- switch `LocalDateTime` scalars to `yyyy-MM-ddTHH:mm:ss` format in `chrono::NaiveDateTime` and `time::PrimitiveDateTime` types - switch from `Date` scalar to `LocalDate` scalar in `chrono::NaiveDate` and `time::Date` types - switch from `UtcDateTime` scalar to `DateTime` scalar in `bson::DateTime` type - correct `TimeZone` scalar in `chrono_tz::Tz` type - rename `Url` scalar to `URL` in `url::Url` type - rename `Uuid` scalar to `UUID` in `uuid::Uuid` type
This commit is contained in:
parent
2756260000
commit
b3a7ffc6a2
12 changed files with 536 additions and 190 deletions
|
@ -385,35 +385,35 @@ mod date_scalar {
|
|||
|
||||
[Juniper] provides out-of-the-box [GraphQL scalar][0] implementations for some very common [Rust] crates. The types from these crates will be usable in your schemas automatically after enabling the correspondent self-titled [Cargo feature].
|
||||
|
||||
| [Rust] type | [GraphQL] scalar | [Cargo feature] |
|
||||
|-----------------------------|------------------|------------------|
|
||||
| [`BigDecimal`] | `BigDecimal` | [`bigdecimal`] |
|
||||
| [`bson::oid::ObjectId`] | `ObjectId` | [`bson`] |
|
||||
| [`bson::DateTime`] | `UtcDateTime` | [`bson`] |
|
||||
| [`chrono::NaiveDate`] | [`Date`] | [`chrono`] |
|
||||
| [`chrono::NaiveTime`] | [`LocalTime`] | [`chrono`] |
|
||||
| [`chrono::NaiveDateTime`] | `LocalDateTime` | [`chrono`] |
|
||||
| [`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`] |
|
||||
| [`time::OffsetDateTime`] | [`DateTime`] | [`time`] |
|
||||
| [`time::UtcOffset`] | [`UtcOffset`] | [`time`] |
|
||||
| [`Url`] | `Url` | [`url`] |
|
||||
| [`Uuid`] | `Uuid` | [`uuid`] |
|
||||
| [Rust] type | [GraphQL] scalar | [Cargo feature] |
|
||||
|-----------------------------|-------------------|------------------|
|
||||
| [`bigdecimal::BigDecimal`] | `BigDecimal` | [`bigdecimal`] |
|
||||
| [`bson::oid::ObjectId`] | `ObjectId` | [`bson`] |
|
||||
| [`bson::DateTime`] | [`DateTime`] | [`bson`] |
|
||||
| [`chrono::NaiveDate`] | [`LocalDate`] | [`chrono`] |
|
||||
| [`chrono::NaiveTime`] | [`LocalTime`] | [`chrono`] |
|
||||
| [`chrono::NaiveDateTime`] | [`LocalDateTime`] | [`chrono`] |
|
||||
| [`chrono::DateTime`] | [`DateTime`] | [`chrono`] |
|
||||
| [`chrono_tz::Tz`] | [`TimeZone`] | [`chrono-tz`] |
|
||||
| [`rust_decimal::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`] | [`LocalDate`] | [`time`] |
|
||||
| [`time::Time`] | [`LocalTime`] | [`time`] |
|
||||
| [`time::PrimitiveDateTime`] | [`LocalDateTime`] | [`time`] |
|
||||
| [`time::OffsetDateTime`] | [`DateTime`] | [`time`] |
|
||||
| [`time::UtcOffset`] | [`UtcOffset`] | [`time`] |
|
||||
| [`url::Url`] | [`URL`] | [`url`] |
|
||||
| [`uuid::Uuid`] | [`UUID`] | [`uuid`] |
|
||||
|
||||
|
||||
|
||||
|
||||
[`bigdecimal`]: https://docs.rs/bigdecimal
|
||||
[`BigDecimal`]: https://docs.rs/bigdecimal/latest/bigdecimal/struct.BigDecimal.html
|
||||
[`bigdecimal::BigDecimal`]: https://docs.rs/bigdecimal/latest/bigdecimal/struct.BigDecimal.html
|
||||
[`bson`]: https://docs.rs/bson
|
||||
[`bson::DateTime`]: https://docs.rs/bson/latest/bson/struct.DateTime.html
|
||||
[`bson::oid::ObjectId`]: https://docs.rs/bson/latest/bson/oid/struct.ObjectId.html
|
||||
|
@ -424,7 +424,6 @@ mod date_scalar {
|
|||
[`chrono::NaiveTime`]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html
|
||||
[`chrono-tz`]: https://docs.rs/chrono-tz
|
||||
[`chrono_tz::Tz`]: https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html
|
||||
[`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
|
||||
|
@ -436,6 +435,7 @@ mod date_scalar {
|
|||
[`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
|
||||
[`LocalDateTime`]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
[`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
|
||||
|
@ -446,11 +446,14 @@ mod date_scalar {
|
|||
[`time::Time`]: https://docs.rs/time/latest/time/struct.Time.html
|
||||
[`time::UtcOffset`]: https://docs.rs/time/latest/time/struct.UtcOffset.html
|
||||
[`time::OffsetDateTime`]: https://docs.rs/time/latest/time/struct.OffsetDateTime.html
|
||||
[`TimeZone`]: https://graphql-scalars.dev/docs/scalars/time-zone
|
||||
[`url`]: https://docs.rs/url
|
||||
[`Url`]: https://docs.rs/url/latest/url/struct.Url.html
|
||||
[`url::Url`]: https://docs.rs/url/latest/url/struct.Url.html
|
||||
[`URL`]: https://graphql-scalars.dev/docs/scalars/url
|
||||
[`UtcOffset`]: https://graphql-scalars.dev/docs/scalars/utc-offset
|
||||
[`uuid`]: https://docs.rs/uuid
|
||||
[`Uuid`]: https://docs.rs/uuid/latest/uuid/struct.Uuid.html
|
||||
[`uuid::Uuid`]: https://docs.rs/uuid/latest/uuid/struct.Uuid.html
|
||||
[`UUID`]: https://graphql-scalars.dev/docs/scalars/uuid
|
||||
[Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html
|
||||
[GraphQL]: https://graphql.org
|
||||
[Juniper]: https://docs.rs/juniper
|
||||
|
|
|
@ -14,19 +14,41 @@ 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])
|
||||
- Corrected compliance with newer [graphql-scalars.dev] specs: ([#1275])
|
||||
- Switched `LocalDateTime` scalars to `yyyy-MM-ddTHH:mm:ss` format in types:
|
||||
- `chrono::NaiveDateTime`.
|
||||
- `time::PrimitiveDateTime`.
|
||||
- Switched from `Date` scalar to `LocalDate` scalar in types:
|
||||
- `chrono::NaiveDate`.
|
||||
- `time::Date`.
|
||||
- Switched from `UtcDateTime` scalar to `DateTime` scalar in types:
|
||||
- `bson::DateTime`.
|
||||
- Corrected `TimeZone` scalar in types:
|
||||
- `chrono_tz::Tz`.
|
||||
- Renamed `Url` scalar to `URL` in types:
|
||||
- `url::Url`.
|
||||
- Renamed `Uuid` scalar to `UUID` in types:
|
||||
- `uuid::Uuid`.
|
||||
|
||||
### Added
|
||||
|
||||
- [`jiff` crate] integration behind `jiff` [Cargo feature]. ([#1271])
|
||||
- [`jiff` crate] integration behind `jiff` [Cargo feature]: ([#1271], [#1270])
|
||||
- `jiff::civil::Date` as `LocalDate` scalar.
|
||||
- `jiff::civil::Time` as `LocalTime` scalar.
|
||||
- `jiff::civil::DateTime` as `LocalDateTime` scalar. ([#1275])
|
||||
- `jiff::Timestamp` as `DateTime` scalar.
|
||||
- `jiff::Span` as `Duration` scalar.
|
||||
|
||||
### 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
|
||||
[#1270]: /../../issues/1270
|
||||
[#1271]: /../../pull/1271
|
||||
[#1272]: /../../pull/1272
|
||||
[#1274]: /../../pull/1274
|
||||
[#1275]: /../../pull/1275
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ anyhow = { version = "1.0.47", optional = true }
|
|||
async-trait = "0.1.39"
|
||||
auto_enums = "0.8"
|
||||
bigdecimal = { version = "0.4", optional = true }
|
||||
bson = { version = "2.4", features = ["chrono-0_4"], optional = true }
|
||||
bson = { version = "2.4", optional = true }
|
||||
chrono = { version = "0.4.30", features = ["alloc"], default-features = false, optional = true }
|
||||
chrono-tz = { version = "0.9", default-features = false, optional = true }
|
||||
fnv = "1.0.5"
|
||||
|
|
|
@ -6,6 +6,7 @@ mod interface {
|
|||
GraphQLObject,
|
||||
};
|
||||
|
||||
#[allow(dead_code)] // TODO: Consider this for the GraphQL interfaces in the expansion.
|
||||
#[graphql_interface(for = [Cat, Dog])]
|
||||
trait Pet {
|
||||
fn name(&self) -> &str;
|
||||
|
|
|
@ -24,6 +24,7 @@ enum Sample {
|
|||
struct Scalar(i32);
|
||||
|
||||
/// A sample interface
|
||||
#[allow(dead_code)] // TODO: Consider this for the GraphQL interfaces in the expansion.
|
||||
#[graphql_interface(name = "SampleInterface", for = Root)]
|
||||
trait Interface {
|
||||
/// A sample field in the interface
|
||||
|
|
|
@ -1,8 +1,30 @@
|
|||
//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
|
||||
//! GraphQL support for [`bson`] crate types.
|
||||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-------------------|-------------------|------------------|
|
||||
//! | [`oid::ObjectId`] | HEX string | `ObjectId` |
|
||||
//! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//!
|
||||
//! [`DateTime`]: bson::DateTime
|
||||
//! [`ObjectId`]: bson::oid::ObjectId
|
||||
//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
//! [s4]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[graphql_scalar(with = object_id, parse_token(String))]
|
||||
/// [BSON ObjectId][0] represented as a HEX string.
|
||||
///
|
||||
/// See also [`bson::oid::ObjectId`][2] for details.
|
||||
///
|
||||
/// [0]: https://www.mongodb.com/docs/manual/reference/bson-types#objectid
|
||||
/// [2]: https://docs.rs/bson/*/bson/oid/struct.ObjectId.html
|
||||
#[graphql_scalar(
|
||||
with = object_id,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://www.mongodb.com/docs/manual/reference/bson-types#objectid",
|
||||
)]
|
||||
type ObjectId = bson::oid::ObjectId;
|
||||
|
||||
mod object_id {
|
||||
|
@ -21,32 +43,49 @@ mod object_id {
|
|||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(with = utc_date_time, parse_token(String))]
|
||||
type UtcDateTime = bson::DateTime;
|
||||
/// [BSON date][3] in [RFC 3339][0] format.
|
||||
///
|
||||
/// [BSON datetimes][3] have millisecond precision and are always in UTC (inputs with other
|
||||
/// timezones are coerced).
|
||||
///
|
||||
/// [`DateTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`bson::DateTime`][2] for details.
|
||||
///
|
||||
/// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
/// [2]: https://docs.rs/bson/*/bson/struct.DateTime.html
|
||||
/// [3]: https://www.mongodb.com/docs/manual/reference/bson-types#date
|
||||
#[graphql_scalar(
|
||||
with = date_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
|
||||
)]
|
||||
type DateTime = bson::DateTime;
|
||||
|
||||
mod utc_date_time {
|
||||
mod date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> {
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &DateTime) -> Value<S> {
|
||||
Value::scalar(
|
||||
(*v).try_to_rfc3339_string()
|
||||
.unwrap_or_else(|e| panic!("failed to format `UtcDateTime` as RFC3339: {e}")),
|
||||
.unwrap_or_else(|e| panic!("failed to format `DateTime` as RFC 3339: {e}")),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| {
|
||||
UtcDateTime::parse_rfc3339_str(s)
|
||||
.map_err(|e| format!("Failed to parse `UtcDateTime`: {e}"))
|
||||
DateTime::parse_rfc3339_str(s)
|
||||
.map_err(|e| format!("Failed to parse `DateTime`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bson::{oid::ObjectId, DateTime as UtcDateTime};
|
||||
use bson::oid::ObjectId;
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||
|
||||
|
@ -60,21 +99,139 @@ mod test {
|
|||
|
||||
assert_eq!(parsed, id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod date_time_test {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::DateTime;
|
||||
|
||||
#[test]
|
||||
fn utcdatetime_from_input() {
|
||||
use chrono::{DateTime, Utc};
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
(
|
||||
"2014-11-28T21:00:09+09:00",
|
||||
DateTime::builder()
|
||||
.year(2014)
|
||||
.month(11)
|
||||
.day(28)
|
||||
.hour(12)
|
||||
.second(9)
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09Z",
|
||||
DateTime::builder()
|
||||
.year(2014)
|
||||
.month(11)
|
||||
.day(28)
|
||||
.hour(21)
|
||||
.second(9)
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09+00:00",
|
||||
DateTime::builder()
|
||||
.year(2014)
|
||||
.month(11)
|
||||
.day(28)
|
||||
.hour(21)
|
||||
.second(9)
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"2014-11-28T21:00:09.05+09:00",
|
||||
DateTime::builder()
|
||||
.year(2014)
|
||||
.month(11)
|
||||
.day(28)
|
||||
.hour(12)
|
||||
.second(9)
|
||||
.millisecond(50)
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = DateTime::from_input_value(&input);
|
||||
|
||||
let raw = "2020-03-23T17:38:32.446+00:00";
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{raw}`: {:?}",
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
|
||||
}
|
||||
}
|
||||
|
||||
let parsed: UtcDateTime = FromInputValue::from_input_value(&input).unwrap();
|
||||
let date_time = UtcDateTime::from_chrono(
|
||||
DateTime::parse_from_rfc3339(raw)
|
||||
.unwrap()
|
||||
.with_timezone(&Utc),
|
||||
);
|
||||
#[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-19 14: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_eq!(parsed, date_time);
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
DateTime::builder()
|
||||
.year(1996)
|
||||
.month(12)
|
||||
.day(19)
|
||||
.hour(12)
|
||||
.build()
|
||||
.unwrap(),
|
||||
graphql_input_value!("1996-12-19T12:00:00Z"),
|
||||
),
|
||||
(
|
||||
DateTime::builder()
|
||||
.year(1564)
|
||||
.month(1)
|
||||
.day(30)
|
||||
.hour(5)
|
||||
.minute(3)
|
||||
.second(3)
|
||||
.millisecond(1)
|
||||
.build()
|
||||
.unwrap(),
|
||||
graphql_input_value!("1564-01-30T05:03:03.001Z"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
||||
assert_eq!(actual, expected, "on value: {val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,21 @@
|
|||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-------------------|-----------------------|-------------------|
|
||||
//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] |
|
||||
//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
|
||||
//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` |
|
||||
//! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-------------------|-----------------------|-----------------------|
|
||||
//! | [`NaiveDate`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
|
||||
//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
|
||||
//! | [`NaiveDateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
|
||||
//! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//!
|
||||
//! [`DateTime`]: chrono::DateTime
|
||||
//! [`NaiveDate`]: chrono::naive::NaiveDate
|
||||
//! [`NaiveDateTime`]: chrono::naive::NaiveDateTime
|
||||
//! [`NaiveTime`]: chrono::naive::NaiveTime
|
||||
//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
//! [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
|
||||
|
||||
use std::fmt;
|
||||
|
@ -29,42 +30,43 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
|||
/// Represents a description of the date (as used for birthdays, for example).
|
||||
/// It cannot represent an instant on the time-line.
|
||||
///
|
||||
/// [`Date` scalar][1] compliant.
|
||||
/// [`LocalDate` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`chrono::NaiveDate`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
/// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html
|
||||
#[graphql_scalar(
|
||||
with = date,
|
||||
with = local_date,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date",
|
||||
)]
|
||||
pub type Date = chrono::NaiveDate;
|
||||
pub type LocalDate = chrono::NaiveDate;
|
||||
|
||||
mod date {
|
||||
mod local_date {
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`Date` scalar][1].
|
||||
/// Format of a [`LocalDate` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
const FORMAT: &str = "%Y-%m-%d";
|
||||
|
||||
pub(super) fn to_output<S>(v: &Date) -> Value<S>
|
||||
pub(super) fn to_output<S>(v: &LocalDate) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Value::scalar(v.format(FORMAT).to_string())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<Date, 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| {
|
||||
Date::parse_from_str(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}"))
|
||||
LocalDate::parse_from_str(s, FORMAT)
|
||||
.map_err(|e| format!("Invalid `LocalDate`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -140,19 +142,28 @@ mod local_time {
|
|||
}
|
||||
}
|
||||
|
||||
/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format.
|
||||
/// Combined date and time (without time zone) in `yyyy-MM-ddTHH:mm:ss` format.
|
||||
///
|
||||
/// See also [`chrono::NaiveDateTime`][1] for details.
|
||||
/// [`LocalDateTime` scalar][1] compliant.
|
||||
///
|
||||
/// [1]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html
|
||||
#[graphql_scalar(with = local_date_time, parse_token(String))]
|
||||
/// See also [`chrono::NaiveDateTime`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
/// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.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 = chrono::NaiveDateTime;
|
||||
|
||||
mod local_date_time {
|
||||
use super::*;
|
||||
|
||||
/// Format of a `LocalDateTime` scalar.
|
||||
const FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||
/// Format of a [`LocalDateTime` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
const FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
|
||||
|
||||
pub(super) fn to_output<S>(v: &LocalDateTime) -> Value<S>
|
||||
where
|
||||
|
@ -329,19 +340,19 @@ impl FromFixedOffset for chrono_tz::Tz {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod date_test {
|
||||
mod local_date_test {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::Date;
|
||||
use super::LocalDate;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
("1996-12-19", Date::from_ymd_opt(1996, 12, 19)),
|
||||
("1564-01-30", Date::from_ymd_opt(1564, 01, 30)),
|
||||
("1996-12-19", LocalDate::from_ymd_opt(1996, 12, 19)),
|
||||
("1564-01-30", LocalDate::from_ymd_opt(1564, 01, 30)),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = Date::from_input_value(&input);
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
|
@ -369,7 +380,7 @@ mod date_test {
|
|||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = Date::from_input_value(&input);
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
|
@ -379,15 +390,15 @@ mod date_test {
|
|||
fn formats_correctly() {
|
||||
for (val, expected) in [
|
||||
(
|
||||
Date::from_ymd_opt(1996, 12, 19),
|
||||
LocalDate::from_ymd_opt(1996, 12, 19),
|
||||
graphql_input_value!("1996-12-19"),
|
||||
),
|
||||
(
|
||||
Date::from_ymd_opt(1564, 01, 30),
|
||||
LocalDate::from_ymd_opt(1564, 01, 30),
|
||||
graphql_input_value!("1564-01-30"),
|
||||
),
|
||||
(
|
||||
Date::from_ymd_opt(2020, 01, 01),
|
||||
LocalDate::from_ymd_opt(2020, 01, 01),
|
||||
graphql_input_value!("2020-01-01"),
|
||||
),
|
||||
] {
|
||||
|
@ -497,14 +508,14 @@ mod local_date_time_test {
|
|||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
(
|
||||
"1996-12-19 14:23:43",
|
||||
"1996-12-19T14:23:43",
|
||||
LocalDateTime::new(
|
||||
NaiveDate::from_ymd_opt(1996, 12, 19).unwrap(),
|
||||
NaiveTime::from_hms_opt(14, 23, 43).unwrap(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"1564-01-30 14:00:00",
|
||||
"1564-01-30T14:00:00",
|
||||
LocalDateTime::new(
|
||||
NaiveDate::from_ymd_opt(1564, 1, 30).unwrap(),
|
||||
NaiveTime::from_hms_opt(14, 00, 00).unwrap(),
|
||||
|
@ -530,15 +541,17 @@ mod local_date_time_test {
|
|||
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!("1996-12-1914:23:43"),
|
||||
graphql_input_value!("1996-12-19 14:23:43"),
|
||||
graphql_input_value!("1996-12-19Q14:23:43"),
|
||||
graphql_input_value!("1996-12-19T14:23:43Z"),
|
||||
graphql_input_value!("1996-12-19T14:23:43.543"),
|
||||
graphql_input_value!("1996-12-19T14:23"),
|
||||
graphql_input_value!("1996-12-19T14:23:"),
|
||||
graphql_input_value!("1996-12-19T23:78:43"),
|
||||
graphql_input_value!("1996-12-19T23:18:99"),
|
||||
graphql_input_value!("1996-12-19T24:00:00"),
|
||||
graphql_input_value!("1996-12-19T99:02:13"),
|
||||
graphql_input_value!("i'm not even a datetime"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
|
@ -560,14 +573,14 @@ mod local_date_time_test {
|
|||
NaiveDate::from_ymd_opt(1996, 12, 19).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
),
|
||||
graphql_input_value!("1996-12-19 00:00:00"),
|
||||
graphql_input_value!("1996-12-19T00:00:00"),
|
||||
),
|
||||
(
|
||||
LocalDateTime::new(
|
||||
NaiveDate::from_ymd_opt(1564, 1, 30).unwrap(),
|
||||
NaiveTime::from_hms_opt(14, 0, 0).unwrap(),
|
||||
),
|
||||
graphql_input_value!("1564-01-30 14:00:00"),
|
||||
graphql_input_value!("1564-01-30T14:00:00"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
@ -737,7 +750,9 @@ mod integration_test {
|
|||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
};
|
||||
|
||||
use super::{Date, DateTime, FixedOffset, FromFixedOffset, LocalDateTime, LocalTime, TimeZone};
|
||||
use super::{
|
||||
DateTime, FixedOffset, FromFixedOffset, LocalDate, LocalDateTime, LocalTime, TimeZone,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn serializes() {
|
||||
|
@ -784,8 +799,8 @@ mod integration_test {
|
|||
|
||||
#[graphql_object]
|
||||
impl Root {
|
||||
fn date() -> Date {
|
||||
Date::from_ymd_opt(2015, 3, 14).unwrap()
|
||||
fn local_date() -> LocalDate {
|
||||
LocalDate::from_ymd_opt(2015, 3, 14).unwrap()
|
||||
}
|
||||
|
||||
fn local_time() -> LocalTime {
|
||||
|
@ -794,7 +809,7 @@ mod integration_test {
|
|||
|
||||
fn local_date_time() -> LocalDateTime {
|
||||
LocalDateTime::new(
|
||||
Date::from_ymd_opt(2016, 7, 8).unwrap(),
|
||||
LocalDate::from_ymd_opt(2016, 7, 8).unwrap(),
|
||||
LocalTime::from_hms_opt(9, 10, 11).unwrap(),
|
||||
)
|
||||
}
|
||||
|
@ -802,7 +817,7 @@ mod integration_test {
|
|||
fn date_time() -> DateTime<chrono::Utc> {
|
||||
DateTime::from_naive_utc_and_offset(
|
||||
LocalDateTime::new(
|
||||
Date::from_ymd_opt(1996, 12, 20).unwrap(),
|
||||
LocalDate::from_ymd_opt(1996, 12, 20).unwrap(),
|
||||
LocalTime::from_hms_opt(0, 39, 57).unwrap(),
|
||||
),
|
||||
chrono::Utc,
|
||||
|
@ -819,7 +834,7 @@ mod integration_test {
|
|||
}
|
||||
|
||||
const DOC: &str = r#"{
|
||||
date
|
||||
localDate
|
||||
localTime
|
||||
localDateTime
|
||||
dateTime,
|
||||
|
@ -837,9 +852,9 @@ mod integration_test {
|
|||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({
|
||||
"date": "2015-03-14",
|
||||
"localDate": "2015-03-14",
|
||||
"localTime": "16:07:08",
|
||||
"localDateTime": "2016-07-08 09:10:11",
|
||||
"localDateTime": "2016-07-08T09:10:11",
|
||||
"dateTime": "1996-12-20T00:39:57Z",
|
||||
"passDateTime": "2014-11-28T12:00:09Z",
|
||||
"transformDateTime": "2014-11-28T12:00:09Z",
|
||||
|
|
|
@ -2,27 +2,35 @@
|
|||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-----------|--------------------|----------------|
|
||||
//! | [`Tz`] | [IANA database][1] | `TimeZone` |
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-----------|--------------------|------------------|
|
||||
//! | [`Tz`] | [IANA database][1] | [`TimeZone`][s1] |
|
||||
//!
|
||||
//! [`chrono-tz`]: chrono_tz
|
||||
//! [`Tz`]: chrono_tz::Tz
|
||||
//! [1]: http://www.iana.org/time-zones
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/time-zone
|
||||
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
/// Timezone based on [`IANA` database][1].
|
||||
/// Timezone based on [`IANA` database][0].
|
||||
///
|
||||
/// See ["List of tz database time zones"][2] `TZ database name` column for
|
||||
/// See ["List of tz database time zones"][3] `TZ database name` column for
|
||||
/// available names.
|
||||
///
|
||||
/// See also [`chrono_tz::Tz`][3] for detals.
|
||||
/// [`TimeZone` scalar][1] compliant.
|
||||
///
|
||||
/// [1]: https://www.iana.org/time-zones
|
||||
/// [2]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
/// [3]: https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html
|
||||
#[graphql_scalar(with = tz, parse_token(String))]
|
||||
/// See also [`chrono_tz::Tz`][2] for details.
|
||||
///
|
||||
/// [0]: https://www.iana.org/time-zones
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/time-zone
|
||||
/// [2]: https://docs.rs/chrono-tz/*/chrono_tz/enum.Tz.html
|
||||
/// [3]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
#[graphql_scalar(
|
||||
with = tz,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone",
|
||||
)]
|
||||
pub type TimeZone = chrono_tz::Tz;
|
||||
|
||||
mod tz {
|
||||
|
|
|
@ -45,7 +45,7 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
|||
/// 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
|
||||
/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Date.html
|
||||
#[graphql_scalar(
|
||||
with = local_date,
|
||||
parse_token(String),
|
||||
|
@ -91,7 +91,7 @@ mod local_date {
|
|||
/// 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
|
||||
/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Time.html
|
||||
#[graphql_scalar(
|
||||
with = local_time,
|
||||
parse_token(String),
|
||||
|
@ -162,7 +162,7 @@ mod local_time {
|
|||
/// 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
|
||||
/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.DateTime.html
|
||||
#[graphql_scalar(
|
||||
with = local_date_time,
|
||||
parse_token(String),
|
||||
|
@ -176,7 +176,7 @@ mod local_date_time {
|
|||
/// 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";
|
||||
const FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
|
||||
|
||||
pub(super) fn to_output<S>(v: &LocalDateTime) -> Value<S>
|
||||
where
|
||||
|
@ -207,7 +207,7 @@ mod local_date_time {
|
|||
/// 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
|
||||
/// [2]: https://docs.rs/jiff/*/jiff/struct.Timestamp.html
|
||||
#[graphql_scalar(
|
||||
with = date_time,
|
||||
parse_token(String),
|
||||
|
@ -252,7 +252,7 @@ mod date_time {
|
|||
/// 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
|
||||
/// [2]: https://docs.rs/jiff/*/jiff/struct.Span.html
|
||||
#[graphql_scalar(
|
||||
with = duration,
|
||||
parse_token(String),
|
||||
|
@ -444,11 +444,11 @@ mod local_date_time_test {
|
|||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
(
|
||||
"1996-12-19 14:23:43",
|
||||
"1996-12-19T14:23:43",
|
||||
LocalDateTime::constant(1996, 12, 19, 14, 23, 43, 0),
|
||||
),
|
||||
(
|
||||
"1564-01-30 14:00:00",
|
||||
"1564-01-30T14:00:00",
|
||||
LocalDateTime::constant(1564, 1, 30, 14, 00, 00, 0),
|
||||
),
|
||||
] {
|
||||
|
@ -471,15 +471,17 @@ mod local_date_time_test {
|
|||
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!("1996-12-1914:23:43"),
|
||||
graphql_input_value!("1996-12-19 14:23:43"),
|
||||
graphql_input_value!("1996-12-19Q14:23:43"),
|
||||
graphql_input_value!("1996-12-19T14:23:43Z"),
|
||||
graphql_input_value!("1996-12-19T14:23:43.543"),
|
||||
graphql_input_value!("1996-12-19T14:23"),
|
||||
graphql_input_value!("1996-12-19T14:23:"),
|
||||
graphql_input_value!("1996-12-19T23:78:43"),
|
||||
graphql_input_value!("1996-12-19T23:18:99"),
|
||||
graphql_input_value!("1996-12-19T24:00:00"),
|
||||
graphql_input_value!("1996-12-19T99:02:13"),
|
||||
graphql_input_value!("i'm not even a datetime"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
|
@ -498,11 +500,11 @@ mod local_date_time_test {
|
|||
for (val, expected) in [
|
||||
(
|
||||
LocalDateTime::constant(1996, 12, 19, 0, 0, 0, 0),
|
||||
graphql_input_value!("1996-12-19 00:00:00"),
|
||||
graphql_input_value!("1996-12-19T00:00:00"),
|
||||
),
|
||||
(
|
||||
LocalDateTime::constant(1564, 1, 30, 14, 0, 0, 0),
|
||||
graphql_input_value!("1564-01-30 14:00:00"),
|
||||
graphql_input_value!("1564-01-30T14:00:00"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
@ -586,6 +588,7 @@ mod date_time_test {
|
|||
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-19 14: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"),
|
||||
|
@ -639,7 +642,7 @@ mod date_time_test {
|
|||
|
||||
#[cfg(test)]
|
||||
mod duration_test {
|
||||
use jiff::ToSpan;
|
||||
use jiff::ToSpan as _;
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
|
@ -731,3 +734,81 @@ mod duration_test {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod integration_test {
|
||||
use jiff::{civil, tz::TimeZone, ToSpan as _};
|
||||
|
||||
use crate::{
|
||||
execute, graphql_object, graphql_value, graphql_vars,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
};
|
||||
|
||||
use super::{DateTime, Duration, LocalDate, LocalDateTime, LocalTime};
|
||||
|
||||
#[tokio::test]
|
||||
async fn serializes() {
|
||||
struct Root;
|
||||
|
||||
#[graphql_object]
|
||||
impl Root {
|
||||
fn local_date() -> LocalDate {
|
||||
LocalDate::constant(2015, 3, 14)
|
||||
}
|
||||
|
||||
fn local_time() -> LocalTime {
|
||||
LocalTime::constant(16, 7, 8, 0)
|
||||
}
|
||||
|
||||
fn local_date_time() -> LocalDateTime {
|
||||
LocalDateTime::constant(2016, 7, 8, 9, 10, 11, 0)
|
||||
}
|
||||
|
||||
fn date_time() -> DateTime {
|
||||
civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 50_000_000)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp()
|
||||
}
|
||||
|
||||
fn duration() -> Duration {
|
||||
1.year()
|
||||
.months(1)
|
||||
.days(1)
|
||||
.hours(1)
|
||||
.minutes(1)
|
||||
.seconds(1)
|
||||
.milliseconds(100)
|
||||
}
|
||||
}
|
||||
|
||||
const DOC: &str = r#"{
|
||||
localDate
|
||||
localTime
|
||||
localDateTime
|
||||
dateTime,
|
||||
duration,
|
||||
}"#;
|
||||
|
||||
let schema = RootNode::new(
|
||||
Root,
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({
|
||||
"localDate": "2015-03-14",
|
||||
"localTime": "16:07:08",
|
||||
"localDateTime": "2016-07-08T09:10:11",
|
||||
"dateTime": "2014-11-28T12:00:09.05Z",
|
||||
"duration": "P1y1m1dT1h1m1.1s",
|
||||
}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-----------------------|-----------------------|---------------------|
|
||||
//! | [`Date`] | `yyyy-MM-dd` | [`Date`][s1] |
|
||||
//! | [`Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
|
||||
//! | [`PrimitiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` |
|
||||
//! | [`OffsetDateTime`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//! | [`UtcOffset`] | `±hh:mm` | [`UtcOffset`][s5] |
|
||||
//! | Rust type | Format | GraphQL scalar |
|
||||
//! |-----------------------|-----------------------|-----------------------|
|
||||
//! | [`Date`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
|
||||
//! | [`Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
|
||||
//! | [`PrimitiveDateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
|
||||
//! | [`OffsetDateTime`] | [RFC 3339] string | [`DateTime`][s4] |
|
||||
//! | [`UtcOffset`] | `±hh:mm` | [`UtcOffset`][s5] |
|
||||
//!
|
||||
//! [`Date`]: time::Date
|
||||
//! [`OffsetDateTime`]: time::OffsetDateTime
|
||||
|
@ -16,8 +16,9 @@
|
|||
//! [`Time`]: time::Time
|
||||
//! [`UtcOffset`]: time::UtcOffset
|
||||
//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
//! [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/utc-offset
|
||||
|
||||
|
@ -33,38 +34,40 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
|||
/// Represents a description of the date (as used for birthdays, for example).
|
||||
/// It cannot represent an instant on the time-line.
|
||||
///
|
||||
/// [`Date` scalar][1] compliant.
|
||||
/// [`LocalDate` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::Date`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
/// [2]: https://docs.rs/time/*/time/struct.Date.html
|
||||
#[graphql_scalar(
|
||||
with = date,
|
||||
with = local_date,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date",
|
||||
)]
|
||||
pub type Date = time::Date;
|
||||
pub type LocalDate = time::Date;
|
||||
|
||||
mod date {
|
||||
mod local_date {
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`Date` scalar][1].
|
||||
/// Format of a [`LocalDate` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
|
||||
const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]");
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> {
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &LocalDate) -> Value<S> {
|
||||
Value::scalar(
|
||||
v.format(FORMAT)
|
||||
.unwrap_or_else(|e| panic!("failed to format `Date`: {e}")),
|
||||
.unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}")),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Date, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalDate, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| Date::parse(s, FORMAT).map_err(|e| format!("Invalid `Date`: {e}")))
|
||||
.and_then(|s| {
|
||||
LocalDate::parse(s, FORMAT).map_err(|e| format!("Invalid `LocalDate`: {e}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,20 +132,29 @@ mod local_time {
|
|||
}
|
||||
}
|
||||
|
||||
/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format.
|
||||
/// Combined date and time (without time zone) in `yyyy-MM-ddTHH:mm:ss` format.
|
||||
///
|
||||
/// [`LocalDateTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::PrimitiveDateTime`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
/// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html
|
||||
#[graphql_scalar(with = local_date_time, parse_token(String))]
|
||||
#[graphql_scalar(
|
||||
with = local_date_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time",
|
||||
)]
|
||||
pub type LocalDateTime = time::PrimitiveDateTime;
|
||||
|
||||
mod local_date_time {
|
||||
use super::*;
|
||||
|
||||
/// Format of a [`LocalDateTime`] scalar.
|
||||
/// Format of a [`LocalDateTime` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
|
||||
const FORMAT: &[BorrowedFormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> {
|
||||
Value::scalar(
|
||||
|
@ -243,12 +255,12 @@ mod utc_offset {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod date_test {
|
||||
mod local_date_test {
|
||||
use time::macros::date;
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||
|
||||
use super::Date;
|
||||
use super::LocalDate;
|
||||
|
||||
#[test]
|
||||
fn parses_correct_input() {
|
||||
|
@ -257,7 +269,7 @@ mod date_test {
|
|||
("1564-01-30", date!(1564 - 01 - 30)),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = Date::from_input_value(&input);
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
|
@ -285,7 +297,7 @@ mod date_test {
|
|||
graphql_input_value!(false),
|
||||
] {
|
||||
let input: InputValue = input;
|
||||
let parsed = Date::from_input_value(&input);
|
||||
let parsed = LocalDate::from_input_value(&input);
|
||||
|
||||
assert!(parsed.is_err(), "allows input: {input:?}");
|
||||
}
|
||||
|
@ -392,8 +404,8 @@ mod local_date_time_test {
|
|||
#[test]
|
||||
fn parses_correct_input() {
|
||||
for (raw, expected) in [
|
||||
("1996-12-19 14:23:43", datetime!(1996-12-19 14:23:43)),
|
||||
("1564-01-30 14:00:00", datetime!(1564-01-30 14:00)),
|
||||
("1996-12-19T14:23:43", datetime!(1996-12-19 14:23:43)),
|
||||
("1564-01-30T14:00:00", datetime!(1564-01-30 14:00)),
|
||||
] {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = LocalDateTime::from_input_value(&input);
|
||||
|
@ -415,16 +427,17 @@ mod local_date_time_test {
|
|||
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-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:1"),
|
||||
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!("1996-12-19 14:23:43"),
|
||||
graphql_input_value!("1996-12-19Q14:23:43"),
|
||||
graphql_input_value!("1996-12-19T14:23:43Z"),
|
||||
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:43"),
|
||||
graphql_input_value!("1996-12-19T23:18:99"),
|
||||
graphql_input_value!("1996-12-19T24:00:00"),
|
||||
graphql_input_value!("1996-12-19T99:02:13"),
|
||||
graphql_input_value!("i'm not even a datetime"),
|
||||
graphql_input_value!(2.32),
|
||||
graphql_input_value!(1),
|
||||
|
@ -443,11 +456,11 @@ mod local_date_time_test {
|
|||
for (val, expected) in [
|
||||
(
|
||||
datetime!(1996-12-19 12:00 am),
|
||||
graphql_input_value!("1996-12-19 00:00:00"),
|
||||
graphql_input_value!("1996-12-19T00:00:00"),
|
||||
),
|
||||
(
|
||||
datetime!(1564-01-30 14:00),
|
||||
graphql_input_value!("1564-01-30 14:00:00"),
|
||||
graphql_input_value!("1564-01-30T14:00:00"),
|
||||
),
|
||||
] {
|
||||
let actual: InputValue = val.to_input_value();
|
||||
|
@ -630,7 +643,7 @@ mod integration_test {
|
|||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
};
|
||||
|
||||
use super::{Date, DateTime, LocalDateTime, LocalTime, UtcOffset};
|
||||
use super::{DateTime, LocalDate, LocalDateTime, LocalTime, UtcOffset};
|
||||
|
||||
#[tokio::test]
|
||||
async fn serializes() {
|
||||
|
@ -638,7 +651,7 @@ mod integration_test {
|
|||
|
||||
#[graphql_object]
|
||||
impl Root {
|
||||
fn date() -> Date {
|
||||
fn local_date() -> LocalDate {
|
||||
date!(2015 - 03 - 14)
|
||||
}
|
||||
|
||||
|
@ -660,7 +673,7 @@ mod integration_test {
|
|||
}
|
||||
|
||||
const DOC: &str = r#"{
|
||||
date
|
||||
localDate
|
||||
localTime
|
||||
localDateTime
|
||||
dateTime,
|
||||
|
@ -677,9 +690,9 @@ mod integration_test {
|
|||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({
|
||||
"date": "2015-03-14",
|
||||
"localDate": "2015-03-14",
|
||||
"localTime": "16:07:08",
|
||||
"localDateTime": "2016-07-08 09:10:11",
|
||||
"localDateTime": "2016-07-08T09:10:11",
|
||||
"dateTime": "1996-12-20T00:39:57Z",
|
||||
"utcOffset": "+11:30",
|
||||
}),
|
||||
|
|
|
@ -1,8 +1,32 @@
|
|||
//! GraphQL support for [url](https://github.com/servo/rust-url) types.
|
||||
//! GraphQL support for [`url`] crate types.
|
||||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | GraphQL scalar |
|
||||
//! |-----------|----------------|
|
||||
//! | [`Url`] | [`URL`][s1] |
|
||||
//!
|
||||
//! [`Url`]: url::Url
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/url
|
||||
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[graphql_scalar(with = url_scalar, parse_token(String))]
|
||||
/// [Standard URL][0] format as specified in [RFC 3986].
|
||||
///
|
||||
/// [`URL` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`url::Url`][2] for details.
|
||||
///
|
||||
/// [0]: http://url.spec.whatwg.org
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/url
|
||||
/// [2]: https://docs.rs/url/*/url/struct.Url.html
|
||||
/// [RFC 3986]: https://datatracker.ietf.org/doc/html/rfc3986
|
||||
#[graphql_scalar(
|
||||
name = "URL",
|
||||
with = url_scalar,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/url",
|
||||
)]
|
||||
type Url = url::Url;
|
||||
|
||||
mod url_scalar {
|
||||
|
@ -15,7 +39,7 @@ mod url_scalar {
|
|||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Url, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {e}")))
|
||||
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `URL`: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,31 @@
|
|||
//! GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types.
|
||||
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
//! GraphQL support for [`uuid`] crate types.
|
||||
//!
|
||||
//! # Supported types
|
||||
//!
|
||||
//! | Rust type | GraphQL scalar |
|
||||
//! |-----------|----------------|
|
||||
//! | [`Uuid`] | [`UUID`][s1] |
|
||||
//!
|
||||
//! [`Uuid`]: uuid::Uuid
|
||||
//! [s1]: https://graphql-scalars.dev/docs/scalars/uuid
|
||||
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[graphql_scalar(with = uuid_scalar, parse_token(String))]
|
||||
/// [Universally Unique Identifier][0] (UUID).
|
||||
///
|
||||
/// [`UUID` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`uuid::Uuid`][2] for details.
|
||||
///
|
||||
/// [0]: https://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/uuid
|
||||
/// [2]: https://docs.rs/uuid/*/uuid/struct.Uuid.html
|
||||
#[graphql_scalar(
|
||||
name = "UUID",
|
||||
with = uuid_scalar,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/uuid",
|
||||
)]
|
||||
type Uuid = uuid::Uuid;
|
||||
|
||||
mod uuid_scalar {
|
||||
|
@ -17,7 +38,7 @@ mod uuid_scalar {
|
|||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Uuid, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {v}"))
|
||||
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {e}")))
|
||||
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `UUID`: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue