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:
Kai Ren 2024-08-16 01:30:05 +02:00 committed by GitHub
parent 2756260000
commit b3a7ffc6a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 536 additions and 190 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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;

View file

@ -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

View file

@ -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}");
}
}
}

View file

@ -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",

View file

@ -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 {

View file

@ -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![],
)),
);
}
}

View file

@ -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",
}),

View file

@ -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}")))
}
}

View file

@ -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}")))
}
}