Skip to content

Commit

Permalink
Merge branch 'master' into graphql-scalars
Browse files Browse the repository at this point in the history
# Conflicts:
#	book/src/types/scalars.md
#	juniper/src/integrations/bson.rs
#	juniper/src/integrations/chrono.rs
#	juniper/src/integrations/chrono_tz.rs
#	juniper/src/integrations/time.rs
#	juniper/src/integrations/url.rs
#	juniper/src/integrations/uuid.rs
  • Loading branch information
tyranron committed Aug 15, 2024
2 parents 1f45fff + b3a7ffc commit df17a80
Show file tree
Hide file tree
Showing 12 changed files with 475 additions and 160 deletions.
27 changes: 13 additions & 14 deletions book/src/types/scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,33 +387,33 @@ mod date_scalar {

| [Rust] type | [GraphQL] scalar | [Cargo feature] |
|-----------------------------|-------------------|------------------|
| [`BigDecimal`] | `BigDecimal` | [`bigdecimal`] |
| [`bigdecimal::BigDecimal`] | `BigDecimal` | [`bigdecimal`] |
| [`bson::oid::ObjectId`] | [`ObjectID`] | [`bson`] |
| [`bson::DateTime`] | `UtcDateTime` | [`bson`] |
| [`chrono::NaiveDate`] | [`Date`] | [`chrono`] |
| [`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`] |
| [`Decimal`] | `Decimal` | [`rust_decimal`] |
| [`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`] | [`Date`] | [`time`] |
| [`time::Date`] | [`LocalDate`] | [`time`] |
| [`time::Time`] | [`LocalTime`] | [`time`] |
| [`time::PrimitiveDateTime`] | [`LocalDateTime`] | [`time`] |
| [`time::OffsetDateTime`] | [`DateTime`] | [`time`] |
| [`time::UtcOffset`] | [`UtcOffset`] | [`time`] |
| [`Url`] | [`URL`] | [`url`] |
| [`Uuid`] | [`UUID`] | [`uuid`] |
| [`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
Expand All @@ -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
Expand All @@ -448,14 +447,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://the-guild.dev/graphql/scalars/docs/scalars/time-zone
[`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
[`URL`]: https://the-guild.dev/graphql/scalars/docs/scalars/url
[`UUID`]: https://the-guild.dev/graphql/scalars/docs/scalars/uuid
[`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
Expand Down
24 changes: 23 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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



Expand Down
2 changes: 1 addition & 1 deletion juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions juniper/src/executor_tests/interfaces_unions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions juniper/src/executor_tests/introspection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
197 changes: 175 additions & 22 deletions juniper/src/integrations/bson.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
//! 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};

/// [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),
Expand All @@ -25,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};

Expand All @@ -64,21 +99,139 @@ mod test {

assert_eq!(parsed, id);
}
}

#[test]
fn utcdatetime_from_input() {
use chrono::{DateTime, Utc};
#[cfg(test)]
mod date_time_test {
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};

let raw = "2020-03-23T17:38:32.446+00:00";
let input: InputValue = graphql_input_value!((raw));
use super::DateTime;

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

assert!(
parsed.is_ok(),
"failed to parse `{raw}`: {:?}",
parsed.unwrap_err(),
);
assert_eq!(parsed.unwrap(), expected, "input: {raw}");
}
}

#[test]
fn fails_on_invalid_input() {
for input in [
graphql_input_value!("12"),
graphql_input_value!("12:"),
graphql_input_value!("56:34:22"),
graphql_input_value!("56:34:22.000"),
graphql_input_value!("1996-12-1914:23:43"),
graphql_input_value!("1996-12-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!(parsed.is_err(), "allows input: {input:?}");
}
}

assert_eq!(parsed, date_time);
#[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}");
}
}
}
Loading

0 comments on commit df17a80

Please sign in to comment.