diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c602cee28..12c8e3e4d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -107,6 +107,7 @@ jobs:
- { feature: chrono-clock, crate: juniper }
- { feature: chrono-tz, crate: juniper }
- { feature: expose-test-schema, crate: juniper }
+ - { feature: jiff, crate: juniper }
- { feature: rust_decimal, crate: juniper }
- { feature: schema-language, crate: juniper }
- { feature: time, crate: juniper }
@@ -144,13 +145,13 @@ jobs:
strategy:
fail-fast: false
matrix:
- msrv: ["1.73.0"]
+ msrv: ["1.75.0"]
crate:
- juniper_codegen
- juniper
- juniper_subscriptions
- juniper_graphql_ws
- #- juniper_actix
+ - juniper_actix
- juniper_axum
- juniper_hyper
- juniper_rocket
@@ -159,10 +160,10 @@ jobs:
- ubuntu
- macOS
- windows
- include:
- - { msrv: "1.75.0", crate: "juniper_actix", os: "ubuntu" }
- - { msrv: "1.75.0", crate: "juniper_actix", os: "macOS" }
- - { msrv: "1.75.0", crate: "juniper_actix", os: "windows" }
+ #include:
+ # - { msrv: "1.75.0", crate: "juniper_actix", os: "ubuntu" }
+ # - { msrv: "1.75.0", crate: "juniper_actix", os: "macOS" }
+ # - { msrv: "1.75.0", crate: "juniper_actix", os: "windows" }
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
diff --git a/README.md b/README.md
index 3f8f41008..2845a4022 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@ your Schemas automatically.
- [url][url]
- [chrono][chrono]
- [chrono-tz][chrono-tz]
+- [jiff][jiff]
- [time][time]
- [bson][bson]
@@ -119,6 +120,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
[url]: https://crates.io/crates/url
[chrono]: https://crates.io/crates/chrono
[chrono-tz]: https://crates.io/crates/chrono-tz
+[jiff]: https://crates.io/crates/jiff
[time]: https://crates.io/crates/time
[bson]: https://crates.io/crates/bson
[juniper-from-schema]: https://github.com/davidpdrsn/juniper-from-schema
diff --git a/book/src/introduction.md b/book/src/introduction.md
index c23e773c1..6d0cf218f 100644
--- a/book/src/introduction.md
+++ b/book/src/introduction.md
@@ -31,6 +31,7 @@ Introduction
- [`bigdecimal`]
- [`bson`]
- [`chrono`], [`chrono-tz`]
+- [`jiff`]
- [`rust_decimal`]
- [`time`]
- [`url`]
@@ -63,6 +64,7 @@ Introduction
[`bson`]: https://docs.rs/bson
[`chrono`]: https://docs.rs/chrono
[`chrono-tz`]: https://docs.rs/chrono-tz
+[`jiff`]: https://docs.rs/jiff
[`juniper`]: https://docs.rs/juniper
[`juniper_actix`]: https://docs.rs/juniper_actix
[`juniper_axum`]: https://docs.rs/juniper_axum
diff --git a/book/src/types/scalars.md b/book/src/types/scalars.md
index ca1f27067..3efd4b651 100644
--- a/book/src/types/scalars.md
+++ b/book/src/types/scalars.md
@@ -385,30 +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`] |
-| [`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
@@ -419,11 +424,20 @@ 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
[`ID`]: https://spec.graphql.org/October2021#sec-ID
+[`jiff`]: https://docs.rs/jiff
+[`jiff::civil::Date`]: https://docs.rs/jiff/latest/jiff/civil/struct.Date.html
+[`jiff::civil::DateTime`]: https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html
+[`jiff::civil::Time`]: https://docs.rs/jiff/latest/jiff/civil/struct.Time.html
+[`jiff::Span`]: https://docs.rs/jiff/latest/jiff/struct.Span.html
+[`jiff::Timestamp`]: https://docs.rs/jiff/latest/jiff/struct.Timestamp.html
+[`LocalDate`]: https://graphql-scalars.dev/docs/scalars/local-date
+[`LocalDateTime`]: https://graphql-scalars.dev/docs/scalars/local-date-time
[`LocalTime`]: https://graphql-scalars.dev/docs/scalars/local-time
+[`ObjectID`]: https://the-guild.dev/graphql/scalars/docs/scalars/object-id
[`rust_decimal`]: https://docs.rs/rust_decimal
[`ScalarValue`]: https://docs.rs/juniper/0.16.1/juniper/trait.ScalarValue.html
[`serde`]: https://docs.rs/serde
@@ -433,11 +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://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
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index 1c563baa8..1be214fd0 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -6,21 +6,52 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
-## [0.17.0] · 2024-04-?? (unreleased)
-[0.17.0]: /../../tree/juniper-v0.17.0/juniper
+## master
-[Diff](/../../compare/juniper-v0.16.1...juniper-v0.17.0) | [Milestone](/../../milestone/7)
+[Diff](/../../compare/juniper-v0.16.1...master) | [Milestone](/../../milestone/7)
### BC Breaks
- 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], [#1277])
+ - 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`.
+ - Renamed `ObjectId` scalar to `ObjectID` in types: ([#1277])
+ - `bson::oid::ObjectId`.
+
+### Added
+
+- [`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.4.0 version](https://github.com/graphql/graphiql/blob/graphiql%403.4.0/packages/graphiql/CHANGELOG.md#340). ([#1269])
+- Updated [GraphiQL] to [3.7.0 version](https://github.com/graphql/graphiql/blob/graphiql%403.7.0/packages/graphiql/CHANGELOG.md#370). ([#1279])
[#1252]: /../../pull/1252
-[#1269]: /../../pull/1269
+[#1270]: /../../issues/1270
+[#1271]: /../../pull/1271
+[#1272]: /../../pull/1272
+[#1275]: /../../pull/1275
+[#1277]: /../../pull/1277
+[#1279]: /../../pull/1279
@@ -221,12 +252,14 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md).
[`bson` crate]: https://docs.rs/bson
[`chrono` crate]: https://docs.rs/chrono
[`chrono-tz` crate]: https://docs.rs/chrono-tz
+[`jiff` crate]: https://docs.rs/jiff
[`time` crate]: https://docs.rs/time
[Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html
[`graphql-transport-ws` GraphQL over WebSocket Protocol]: https://github.com/enisdenjo/graphql-ws/v5.14.0/PROTOCOL.md
[GraphiQL]: https://github.com/graphql/graphiql
[GraphQL Playground]: https://github.com/prisma/graphql-playground
[graphql-scalars.dev]: https://graphql-scalars.dev
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[October 2021]: https://spec.graphql.org/October2021
[object safety]: https://doc.rust-lang.org/reference/items/traits.html#object-safety
[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml
index f12400b53..6f950a6c3 100644
--- a/juniper/Cargo.toml
+++ b/juniper/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper"
version = "0.16.1"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "GraphQL server library."
license = "BSD-2-Clause"
authors = [
@@ -33,6 +33,7 @@ chrono = ["dep:chrono"]
chrono-clock = ["chrono", "chrono/clock"]
chrono-tz = ["dep:chrono-tz", "dep:regex"]
expose-test-schema = ["dep:anyhow", "dep:serde_json"]
+jiff = ["dep:jiff"]
js = ["chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js"]
rust_decimal = ["dep:rust_decimal"]
schema-language = ["dep:graphql-parser", "dep:void"]
@@ -45,13 +46,14 @@ 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"
futures = { version = "0.3.22", features = ["alloc"], default-features = false }
graphql-parser = { version = "0.4", optional = true }
indexmap = { version = "2.0", features = ["serde"] }
+jiff = { version = "0.1.5", features = ["alloc"], default-features = false, optional = true }
juniper_codegen = { version = "0.16.0", path = "../juniper_codegen" }
rust_decimal = { version = "1.20", default-features = false, optional = true }
ryu = { version = "1.0", optional = true }
diff --git a/juniper/Makefile b/juniper/Makefile
index 30ead9e69..d04febe0d 100644
--- a/juniper/Makefile
+++ b/juniper/Makefile
@@ -39,10 +39,6 @@ graphiql:
https://raw.githubusercontent.com/graphql/graphiql/graphiql%40$(GRAPHIQL_VER)/examples/graphiql-cdn/index.html
$(sed-i) 's|unpkg.com/graphiql/|unpkg.com/graphiql@$(GRAPHIQL_VER)/|g' \
src/http/graphiql.html
- $(sed-i) 's|react.development.js|react.production.min.js|g' \
- src/http/graphiql.html
- $(sed-i) 's|react-dom.development.js|react-dom.production.min.js|g' \
- src/http/graphiql.html
$(sed-i) "s|'https://swapi-graphql.netlify.app/.netlify/functions/index'|JUNIPER_URL|g" \
src/http/graphiql.html
$(sed-i) "s|url: JUNIPER_URL,|url: JUNIPER_URL,\n subscriptionUrl: normalizeSubscriptionEndpoint(JUNIPER_URL, JUNIPER_SUBSCRIPTIONS_URL)|" \
diff --git a/juniper/README.md b/juniper/README.md
index ab321f74e..307fa053a 100644
--- a/juniper/README.md
+++ b/juniper/README.md
@@ -4,7 +4,7 @@ Juniper (GraphQL server library for Rust)
[![Crates.io](https://img.shields.io/crates/v/juniper.svg?maxAge=2592000)](https://crates.io/crates/juniper)
[![Documentation](https://docs.rs/juniper/badge.svg)](https://docs.rs/juniper)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Juniper Book] ([current][Juniper Book] | [edge][Juniper Book edge])
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper-v0.16.1/juniper/CHANGELOG.md)
@@ -48,6 +48,7 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil
- [`bigdecimal`]
- [`bson`]
- [`chrono`], [`chrono-tz`]
+- [`jiff`]
- [`rust_decimal`]
- [`time`]
- [`url`]
@@ -85,6 +86,7 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
[`bson`]: https://docs.rs/bson
[`chrono`]: https://docs.rs/chrono
[`chrono-tz`]: https://docs.rs/chrono-tz
+[`jiff`]: https://docs.rs/jiff
[`juniper_actix`]: https://docs.rs/juniper_actix
[`juniper_axum`]: https://docs.rs/juniper_axum
[`juniper_hyper`]: https://docs.rs/juniper_hyper
diff --git a/juniper/package.json b/juniper/package.json
index eb4a46f19..8124c04e1 100644
--- a/juniper/package.json
+++ b/juniper/package.json
@@ -4,7 +4,7 @@
"postinstall": "make graphiql graphql-playground"
},
"dependencies": {
- "graphiql": "3.4.0",
+ "graphiql": "3.7.0",
"graphql-playground-react": "1.7.28"
}
}
diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs
index 10d5e0889..3023d0197 100644
--- a/juniper/src/executor_tests/interfaces_unions.rs
+++ b/juniper/src/executor_tests/interfaces_unions.rs
@@ -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;
diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs
index ee2501b21..f8de215f1 100644
--- a/juniper/src/executor_tests/introspection/mod.rs
+++ b/juniper/src/executor_tests/introspection/mod.rs
@@ -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
diff --git a/juniper/src/http/graphiql.html b/juniper/src/http/graphiql.html
index 7cf55dec1..9ff0deb80 100644
--- a/juniper/src/http/graphiql.html
+++ b/juniper/src/http/graphiql.html
@@ -42,10 +42,10 @@
favored resource bundler.
-->
-
+
diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs
index 20c88fbef..06957935d 100644
--- a/juniper/src/integrations/bson.rs
+++ b/juniper/src/integrations/bson.rs
@@ -1,8 +1,35 @@
-//! 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`][s1] |
+//! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] |
+//!
+//! [`DateTime`]: bson::DateTime
+//! [`oid::ObjectId`]: bson::oid::ObjectId
+//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
+//! [s1]: https://graphql-scalars.dev/docs/scalars/object-id
+//! [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.
+///
+/// [`ObjectID` scalar][1] compliant.
+///
+/// See also [`bson::oid::ObjectId`][2] for details.
+///
+/// [0]: https://www.mongodb.com/docs/manual/reference/bson-types#objectid
+/// [1]: https://graphql-scalars.dev/docs/scalars/object-id
+/// [2]: https://docs.rs/bson/*/bson/oid/struct.ObjectId.html
+#[graphql_scalar(
+ name = "ObjectID",
+ with = object_id,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/object-id",
+)]
type ObjectId = bson::oid::ObjectId;
mod object_id {
@@ -16,37 +43,54 @@ mod object_id {
v.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {v}"))
.and_then(|s| {
- ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {e}"))
+ ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectID`: {e}"))
})
}
}
-#[graphql_scalar(with = utc_date_time, parse_token(String))]
-type UtcDateTime = bson::DateTime;
-
-mod utc_date_time {
+/// [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 date_time {
use super::*;
- pub(super) fn to_output(v: &UtcDateTime) -> Value {
+ pub(super) fn to_output(v: &DateTime) -> Value {
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(v: &InputValue) -> Result {
+ pub(super) fn from_input(v: &InputValue) -> Result {
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 +104,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}");
+ }
+ }
- assert_eq!(parsed, date_time);
+ #[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:?}");
+ }
+ }
+
+ #[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}");
+ }
}
}
diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs
index c74eaa019..be44cfa0c 100644
--- a/juniper/src/integrations/chrono.rs
+++ b/juniper/src/integrations/chrono.rs
@@ -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(v: &Date) -> Value
+ pub(super) fn to_output(v: &LocalDate) -> Value
where
S: ScalarValue,
{
Value::scalar(v.format(FORMAT).to_string())
}
- pub(super) fn from_input(v: &InputValue) -> Result
+ pub(super) fn from_input(v: &InputValue) -> Result
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(v: &LocalDateTime) -> Value
where
@@ -189,6 +200,7 @@ mod local_date_time {
#[graphql_scalar(
with = date_time,
parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
where(
Tz: TimeZone + FromFixedOffset,
Tz::Offset: fmt::Display,
@@ -329,19 +341,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 +381,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 +391,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 +509,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 +542,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 +574,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 +751,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 +800,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 +810,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 +818,7 @@ mod integration_test {
fn date_time() -> DateTime {
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 +835,7 @@ mod integration_test {
}
const DOC: &str = r#"{
- date
+ localDate
localTime
localDateTime
dateTime,
@@ -837,9 +853,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",
diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs
index 5c30b211b..bac4b5e5d 100644
--- a/juniper/src/integrations/chrono_tz.rs
+++ b/juniper/src/integrations/chrono_tz.rs
@@ -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 {
diff --git a/juniper/src/integrations/jiff.rs b/juniper/src/integrations/jiff.rs
new file mode 100644
index 000000000..bea0bdaf4
--- /dev/null
+++ b/juniper/src/integrations/jiff.rs
@@ -0,0 +1,814 @@
+//! GraphQL support for [`jiff`] crate types.
+//!
+//! # Supported types
+//!
+//! | Rust type | Format | GraphQL scalar |
+//! |---------------------|-----------------------|-----------------------|
+//! | [`civil::Date`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
+//! | [`civil::Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
+//! | [`civil::DateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
+//! | [`Timestamp`] | [RFC 3339] string | [`DateTime`][s4] |
+//! | [`Span`] | [ISO 8601] duration | [`Duration`][s5] |
+//!
+//! # Unsupported types
+//!
+//! [`Zoned`] is not supported because the GraphQL scalar [`DateTime`][s4] only supports time zone
+//! offsets but no IANA time zone names (as in `2024-08-10T23:14:00-04:00[America/New_York]`, cf.
+//! [RFC 9557]). Serializing such values would incur a loss of information with unexpected and
+//! subtle consequences (a fixed offset would only _seem_ to work in most cases).
+//!
+//! [`civil::Date`]: jiff::civil::Date
+//! [`civil::DateTime`]: jiff::civil::DateTime
+//! [`civil::Time`]: jiff::civil::Time
+//! [`Span`]: jiff::Span
+//! [`Timestamp`]: jiff::Timestamp
+//! [`Zoned`]: jiff::Zoned
+//! [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations
+//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
+//! [RFC 9557]: https://datatracker.ietf.org/doc/html/rfc9557#section-4.1
+//! [s1]: https://graphql-scalars.dev/docs/scalars/local-date
+//! [s2]: https://graphql-scalars.dev/docs/scalars/local-time
+//! [s3]: https://graphql-scalars.dev/docs/scalars/local-date-time
+//! [s4]: https://graphql-scalars.dev/docs/scalars/date-time
+//! [s5]: https://graphql-scalars.dev/docs/scalars/duration
+
+use crate::{graphql_scalar, InputValue, ScalarValue, Value};
+
+/// Representation of a civil date in the Gregorian calendar.
+///
+/// Corresponds to a triple of year, month and day. Every value is guaranteed to be a valid
+/// Gregorian calendar date. For example, both `2023-02-29` and `2023-11-31` are invalid and cannot
+/// be represented.
+///
+/// [`LocalDate` scalar][1] compliant.
+///
+/// See also [`jiff::civil::Date`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
+/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Date.html
+#[graphql_scalar(
+ with = local_date,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date",
+)]
+pub type LocalDate = jiff::civil::Date;
+
+mod local_date {
+ use super::*;
+
+ /// Format of a [`LocalDate` scalar][1].
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/local-date
+ const FORMAT: &str = "%Y-%m-%d";
+
+ pub(super) fn to_output(v: &LocalDate) -> Value
+ where
+ S: ScalarValue,
+ {
+ Value::scalar(v.strftime(FORMAT).to_string())
+ }
+
+ pub(super) fn from_input(v: &InputValue) -> Result
+ where
+ S: ScalarValue,
+ {
+ v.as_string_value()
+ .ok_or_else(|| format!("Expected `String`, found: {v}"))
+ .and_then(|s| {
+ LocalDate::strptime(FORMAT, s).map_err(|e| format!("Invalid `LocalDate`: {e}"))
+ })
+ }
+}
+
+/// Representation of a civil "wall clock" time.
+///
+/// Conceptually, corresponds to the typical hours and minutes that you might see on a clock. This
+/// type also contains the second and fractional subsecond (to nanosecond precision) associated with
+/// a time.
+///
+/// [`LocalTime` scalar][1] compliant.
+///
+/// See also [`jiff::civil::Time`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
+/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.Time.html
+#[graphql_scalar(
+ with = local_time,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time",
+)]
+pub type LocalTime = jiff::civil::Time;
+
+mod local_time {
+ use super::*;
+
+ /// Full format of a [`LocalTime` scalar][1].
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/local-time
+ const FORMAT: &str = "%H:%M:%S%.3f";
+
+ /// Format of a [`LocalTime` scalar][1] without milliseconds.
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/local-time
+ const FORMAT_NO_MILLIS: &str = "%H:%M:%S";
+
+ /// Format of a [`LocalTime` scalar][1] without seconds.
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/local-time
+ const FORMAT_NO_SECS: &str = "%H:%M";
+
+ pub(super) fn to_output(v: &LocalTime) -> Value
+ where
+ S: ScalarValue,
+ {
+ Value::scalar(
+ if v.subsec_nanosecond() == 0 {
+ v.strftime(FORMAT_NO_MILLIS)
+ } else {
+ v.strftime(FORMAT)
+ }
+ .to_string(),
+ )
+ }
+
+ pub(super) fn from_input(v: &InputValue) -> Result
+ where
+ S: ScalarValue,
+ {
+ v.as_string_value()
+ .ok_or_else(|| format!("Expected `String`, found: {v}"))
+ .and_then(|s| {
+ // First, try to parse the most used format.
+ // At the end, try to parse the full format for the parsing
+ // error to be most informative.
+ LocalTime::strptime(FORMAT_NO_MILLIS, s)
+ .or_else(|_| LocalTime::strptime(FORMAT_NO_SECS, s))
+ .or_else(|_| LocalTime::strptime(FORMAT, s))
+ .map_err(|e| format!("Invalid `LocalTime`: {e}"))
+ })
+ }
+}
+
+/// Representation of a civil datetime in the Gregorian calendar.
+///
+/// Corresponds to a pair of a `LocalDate` and a `LocalTime`. That is, a datetime contains a year,
+/// month, day, hour, minute, second and the fractional number of nanoseconds.
+///
+/// Value is guaranteed to contain a valid date and time. For example, neither `2023-02-29T00:00:00`
+/// nor `2015-06-30T23:59:60` are valid.
+///
+/// [`LocalDateTime` scalar][1] compliant.
+///
+/// See also [`jiff::civil::DateTime`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
+/// [2]: https://docs.rs/jiff/*/jiff/civil/struct.DateTime.html
+#[graphql_scalar(
+ with = local_date_time,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-date-time",
+)]
+pub type LocalDateTime = jiff::civil::DateTime;
+
+mod local_date_time {
+ use super::*;
+
+ /// Format of a [`LocalDateTime` scalar][1].
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/local-date-time
+ const FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
+
+ pub(super) fn to_output(v: &LocalDateTime) -> Value
+ where
+ S: ScalarValue,
+ {
+ Value::scalar(v.strftime(FORMAT).to_string())
+ }
+
+ pub(super) fn from_input(v: &InputValue) -> Result
+ where
+ S: ScalarValue,
+ {
+ v.as_string_value()
+ .ok_or_else(|| format!("Expected `String`, found: {v}"))
+ .and_then(|s| {
+ LocalDateTime::strptime(FORMAT, s)
+ .map_err(|e| format!("Invalid `LocalDateTime`: {e}"))
+ })
+ }
+}
+
+/// Instant in time represented as the number of nanoseconds since the Unix epoch.
+///
+/// Always in UTC.
+///
+/// [`DateTime` scalar][1] compliant.
+///
+/// See also [`jiff::Timestamp`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
+/// [2]: https://docs.rs/jiff/*/jiff/struct.Timestamp.html
+#[graphql_scalar(
+ with = date_time,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
+)]
+pub type DateTime = jiff::Timestamp;
+
+mod date_time {
+ use std::str::FromStr as _;
+
+ use super::*;
+
+ /// Format of a [`DateTime` scalar][1].
+ ///
+ /// [1]: https://graphql-scalars.dev/docs/scalars/date-time
+ const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ";
+
+ pub(super) fn to_output(v: &DateTime) -> Value
+ where
+ S: ScalarValue,
+ {
+ Value::scalar(v.strftime(FORMAT).to_string())
+ }
+
+ pub(super) fn from_input(v: &InputValue) -> Result
+ where
+ S: ScalarValue,
+ {
+ v.as_string_value()
+ .ok_or_else(|| format!("Expected `String`, found: {v}"))
+ .and_then(|s| DateTime::from_str(s).map_err(|e| format!("Invalid `DateTime`: {e}")))
+ }
+}
+
+/// Span of time represented via a mixture of calendar and clock units.
+///
+/// Represents a duration of time in units of years, months, weeks, days, hours, minutes, seconds,
+/// milliseconds, microseconds and nanoseconds.
+///
+/// [`Duration` scalar][1] compliant.
+///
+/// See also [`jiff::Span`][2] for details.
+///
+/// [1]: https://graphql-scalars.dev/docs/scalars/duration
+/// [2]: https://docs.rs/jiff/*/jiff/struct.Span.html
+#[graphql_scalar(
+ with = duration,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/duration",
+)]
+pub type Duration = jiff::Span;
+
+mod duration {
+ use std::str::FromStr as _;
+
+ use super::*;
+
+ pub(super) fn to_output(v: &Duration) -> Value
+ where
+ S: ScalarValue,
+ {
+ Value::scalar(v.to_string())
+ }
+
+ pub(super) fn from_input(v: &InputValue) -> Result
+ where
+ S: ScalarValue,
+ {
+ v.as_string_value()
+ .ok_or_else(|| format!("Expected `String`, found: {v}"))
+ .and_then(|s| Duration::from_str(s).map_err(|e| format!("Invalid `Duration`: {e}")))
+ }
+}
+
+#[cfg(test)]
+mod local_date_test {
+ use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
+
+ use super::LocalDate;
+
+ #[test]
+ fn parses_correct_input() {
+ for (raw, expected) in [
+ ("1996-12-19", LocalDate::constant(1996, 12, 19)),
+ ("1564-01-30", LocalDate::constant(1564, 01, 30)),
+ ] {
+ let input: InputValue = graphql_input_value!((raw));
+ let parsed = LocalDate::from_input_value(&input);
+
+ assert!(
+ parsed.is_ok(),
+ "failed to parse `{raw}`: {:?}",
+ parsed.unwrap_err(),
+ );
+ assert_eq!(parsed.unwrap(), expected, "input: {raw}");
+ }
+ }
+
+ #[test]
+ fn fails_on_invalid_input() {
+ for input in [
+ graphql_input_value!("1996-13-19"),
+ graphql_input_value!("1564-01-61"),
+ graphql_input_value!("2021-11-31"),
+ graphql_input_value!("11-31"),
+ graphql_input_value!("2021-11"),
+ graphql_input_value!("2021"),
+ graphql_input_value!("31"),
+ graphql_input_value!("i'm not even a date"),
+ graphql_input_value!(2.32),
+ graphql_input_value!(1),
+ graphql_input_value!(null),
+ graphql_input_value!(false),
+ ] {
+ let input: InputValue = input;
+ let parsed = LocalDate::from_input_value(&input);
+
+ assert!(parsed.is_err(), "allows input: {input:?}");
+ }
+ }
+
+ #[test]
+ fn formats_correctly() {
+ for (val, expected) in [
+ (
+ LocalDate::constant(1996, 12, 19),
+ graphql_input_value!("1996-12-19"),
+ ),
+ (
+ LocalDate::constant(1564, 01, 30),
+ graphql_input_value!("1564-01-30"),
+ ),
+ (
+ LocalDate::constant(2020, 01, 01),
+ graphql_input_value!("2020-01-01"),
+ ),
+ ] {
+ let actual: InputValue = val.to_input_value();
+
+ assert_eq!(actual, expected, "on value: {val}");
+ }
+ }
+}
+
+#[cfg(test)]
+mod local_time_test {
+ use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
+
+ use super::LocalTime;
+
+ #[test]
+ fn parses_correct_input() {
+ for (raw, expected) in [
+ ("14:23:43", LocalTime::constant(14, 23, 43, 000_000_000)),
+ ("14:00:00", LocalTime::constant(14, 00, 00, 000_000_000)),
+ ("14:00", LocalTime::constant(14, 00, 00, 000_000_000)),
+ ("14:32", LocalTime::constant(14, 32, 00, 000_000_000)),
+ ("14:00:00.000", LocalTime::constant(14, 00, 00, 000_000_000)),
+ ("14:23:43.345", LocalTime::constant(14, 23, 43, 345_000_000)),
+ ] {
+ let input: InputValue = graphql_input_value!((raw));
+ let parsed = LocalTime::from_input_value(&input);
+
+ assert!(
+ parsed.is_ok(),
+ "failed to parse `{raw}`: {:?}",
+ parsed.unwrap_err(),
+ );
+ assert_eq!(parsed.unwrap(), expected, "input: {raw}");
+ }
+ }
+
+ #[test]
+ fn fails_on_invalid_input() {
+ for input in [
+ graphql_input_value!("12"),
+ graphql_input_value!("12:"),
+ graphql_input_value!("56:34:22"),
+ graphql_input_value!("23:78:43"),
+ graphql_input_value!("23:78:"),
+ graphql_input_value!("23:18:99"),
+ graphql_input_value!("23:18:22."),
+ graphql_input_value!("22.03"),
+ graphql_input_value!("24:00"),
+ graphql_input_value!("24:00:00"),
+ graphql_input_value!("24:00:00.000"),
+ graphql_input_value!("i'm not even a time"),
+ graphql_input_value!(2.32),
+ graphql_input_value!(1),
+ graphql_input_value!(null),
+ graphql_input_value!(false),
+ ] {
+ let input: InputValue = input;
+ let parsed = LocalTime::from_input_value(&input);
+
+ assert!(parsed.is_err(), "allows input: {input:?}");
+ }
+ }
+
+ #[test]
+ fn formats_correctly() {
+ for (val, expected) in [
+ (
+ LocalTime::constant(1, 2, 3, 4_005_000),
+ graphql_input_value!("01:02:03.004"),
+ ),
+ (
+ LocalTime::constant(0, 0, 0, 0),
+ graphql_input_value!("00:00:00"),
+ ),
+ (
+ LocalTime::constant(12, 0, 0, 0),
+ graphql_input_value!("12:00:00"),
+ ),
+ (
+ LocalTime::constant(1, 2, 3, 0),
+ graphql_input_value!("01:02:03"),
+ ),
+ ] {
+ let actual: InputValue = val.to_input_value();
+
+ assert_eq!(actual, expected, "on value: {val}");
+ }
+ }
+}
+
+#[cfg(test)]
+mod local_date_time_test {
+ use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
+
+ use super::LocalDateTime;
+
+ #[test]
+ fn parses_correct_input() {
+ for (raw, expected) in [
+ (
+ "1996-12-19T14:23:43",
+ LocalDateTime::constant(1996, 12, 19, 14, 23, 43, 0),
+ ),
+ (
+ "1564-01-30T14:00:00",
+ LocalDateTime::constant(1564, 1, 30, 14, 00, 00, 0),
+ ),
+ ] {
+ let input: InputValue = graphql_input_value!((raw));
+ let parsed = LocalDateTime::from_input_value(&input);
+
+ assert!(
+ parsed.is_ok(),
+ "failed to parse `{raw}`: {:?}",
+ parsed.unwrap_err(),
+ );
+ assert_eq!(parsed.unwrap(), expected, "input: {raw}");
+ }
+ }
+
+ #[test]
+ fn fails_on_invalid_input() {
+ for input in [
+ graphql_input_value!("12"),
+ graphql_input_value!("12:"),
+ graphql_input_value!("56:34:22"),
+ graphql_input_value!("56:34:22.000"),
+ graphql_input_value!("1996-12-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),
+ graphql_input_value!(null),
+ graphql_input_value!(false),
+ ] {
+ let input: InputValue = input;
+ let parsed = LocalDateTime::from_input_value(&input);
+
+ assert!(parsed.is_err(), "allows input: {input:?}");
+ }
+ }
+
+ #[test]
+ fn formats_correctly() {
+ for (val, expected) in [
+ (
+ LocalDateTime::constant(1996, 12, 19, 0, 0, 0, 0),
+ graphql_input_value!("1996-12-19T00:00:00"),
+ ),
+ (
+ LocalDateTime::constant(1564, 1, 30, 14, 0, 0, 0),
+ graphql_input_value!("1564-01-30T14:00:00"),
+ ),
+ ] {
+ let actual: InputValue = val.to_input_value();
+
+ assert_eq!(actual, expected, "on value: {val}");
+ }
+ }
+}
+
+#[cfg(test)]
+mod date_time_test {
+ use jiff::{civil, tz::TimeZone};
+
+ use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
+
+ use super::DateTime;
+
+ #[test]
+ fn parses_correct_input() {
+ for (raw, expected) in [
+ (
+ "2014-11-28T21:00:09+09:00",
+ civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 0)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ (
+ "2014-11-28T21:00:09Z",
+ civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ (
+ "2014-11-28 21:00:09z",
+ civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ (
+ "2014-11-28T21:00:09+00:00",
+ civil::DateTime::constant(2014, 11, 28, 21, 0, 9, 0)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ (
+ "2014-11-28T21:00:09.05+09:00",
+ civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 50_000_000)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ (
+ "2014-11-28 21:00:09.05+09:00",
+ civil::DateTime::constant(2014, 11, 28, 12, 0, 9, 50_000_000)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ ),
+ ] {
+ let input: InputValue = graphql_input_value!((raw));
+ let parsed = DateTime::from_input_value(&input);
+
+ assert!(
+ parsed.is_ok(),
+ "failed to parse `{raw}`: {:?}",
+ parsed.unwrap_err(),
+ );
+ assert_eq!(parsed.unwrap(), expected, "input: {raw}");
+ }
+ }
+
+ #[test]
+ fn fails_on_invalid_input() {
+ for input in [
+ graphql_input_value!("12"),
+ graphql_input_value!("12:"),
+ graphql_input_value!("56:34:22"),
+ graphql_input_value!("56:34:22.000"),
+ graphql_input_value!("1996-12-1914:23:43"),
+ graphql_input_value!("1996-12-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"),
+ graphql_input_value!("1996-12-19T14:23:43.543"),
+ graphql_input_value!("1996-12-19T14:23"),
+ graphql_input_value!("1996-12-19T14:23:1"),
+ graphql_input_value!("1996-12-19T14:23:"),
+ graphql_input_value!("1996-12-19T23:78:43Z"),
+ graphql_input_value!("1996-12-19T23:18:99Z"),
+ graphql_input_value!("1996-12-19T24:00:00Z"),
+ graphql_input_value!("1996-12-19T99:02:13Z"),
+ graphql_input_value!("1996-12-19T99:02:13Z"),
+ graphql_input_value!("1996-12-19T12:02:13+4444444"),
+ graphql_input_value!("i'm not even a datetime"),
+ graphql_input_value!(2.32),
+ graphql_input_value!(1),
+ graphql_input_value!(null),
+ graphql_input_value!(false),
+ ] {
+ let input: InputValue = input;
+ let parsed = DateTime::from_input_value(&input);
+
+ assert!(parsed.is_err(), "allows input: {input:?}");
+ }
+ }
+
+ #[test]
+ fn formats_correctly() {
+ for (val, expected) in [
+ (
+ civil::DateTime::constant(1996, 12, 19, 0, 0, 0, 0)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ graphql_input_value!("1996-12-19T00:00:00Z"),
+ ),
+ (
+ civil::DateTime::constant(1564, 1, 30, 5, 0, 0, 123_000_000)
+ .to_zoned(TimeZone::UTC)
+ .unwrap()
+ .timestamp(),
+ graphql_input_value!("1564-01-30T05:00:00.123Z"),
+ ),
+ ] {
+ let actual: InputValue = val.to_input_value();
+
+ assert_eq!(actual, expected, "on value: {val}");
+ }
+ }
+}
+
+#[cfg(test)]
+mod duration_test {
+ use jiff::ToSpan as _;
+
+ use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
+
+ use super::Duration;
+
+ #[test]
+ fn parses_correct_input() {
+ for (raw, expected) in [
+ ("P5dT8h1m", 5.days().hours(8).minutes(1)),
+ ("-P5d", (-5).days()),
+ ("P2M10DT2H30M", 2.months().days(10).hours(2).minutes(30)),
+ ("P40D", 40.days()),
+ ("P1y1d", 1.year().days(1)),
+ ("P3dT4h59m", 3.days().hours(4).minutes(59)),
+ ("PT2H30M", 2.hours().minutes(30)),
+ ("P1m", 1.month()),
+ ("P1w", 1.week()),
+ ("P1w4d", 1.week().days(4)),
+ ("PT1m", 1.minute()),
+ ("PT0.0021s", 2.milliseconds().microseconds(100)),
+ ("PT0s", 0.seconds()),
+ ("P0d", 0.seconds()),
+ (
+ "P1y1m1dT1h1m1.1s",
+ 1.year()
+ .months(1)
+ .days(1)
+ .hours(1)
+ .minutes(1)
+ .seconds(1)
+ .milliseconds(100),
+ ),
+ ] {
+ let input: InputValue = graphql_input_value!((raw));
+ let parsed = Duration::from_input_value(&input);
+
+ assert!(
+ parsed.is_ok(),
+ "failed to parse `{raw}`: {:?}",
+ parsed.unwrap_err(),
+ );
+ assert_eq!(parsed.unwrap(), expected, "input: {raw}");
+ }
+ }
+
+ #[test]
+ fn fails_on_invalid_input() {
+ for input in [
+ graphql_input_value!("12"),
+ graphql_input_value!("12S"),
+ graphql_input_value!("P0"),
+ graphql_input_value!("PT"),
+ graphql_input_value!("PTS"),
+ graphql_input_value!("56:34:22"),
+ graphql_input_value!("1996-12-19"),
+ graphql_input_value!("1996-12-19T14:23:43"),
+ graphql_input_value!("1996-12-19T14:23:43Z"),
+ graphql_input_value!("i'm not even a duration"),
+ graphql_input_value!(2.32),
+ graphql_input_value!(1),
+ graphql_input_value!(null),
+ graphql_input_value!(false),
+ ] {
+ let input: InputValue = input;
+ let parsed = Duration::from_input_value(&input);
+
+ assert!(parsed.is_err(), "allows input: {input:?}");
+ }
+ }
+
+ #[test]
+ fn formats_correctly() {
+ for (val, expected) in [
+ (
+ 1.year()
+ .months(1)
+ .days(1)
+ .hours(1)
+ .minutes(1)
+ .seconds(1)
+ .milliseconds(100),
+ graphql_input_value!("P1y1m1dT1h1m1.1s"),
+ ),
+ ((-5).days(), graphql_input_value!("-P5d")),
+ ] {
+ let actual: InputValue = val.to_input_value();
+
+ assert_eq!(actual, expected, "on value: {val}");
+ }
+ }
+}
+
+#[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![],
+ )),
+ );
+ }
+}
diff --git a/juniper/src/integrations/mod.rs b/juniper/src/integrations/mod.rs
index 0d88bdf2c..3f16037e6 100644
--- a/juniper/src/integrations/mod.rs
+++ b/juniper/src/integrations/mod.rs
@@ -10,6 +10,8 @@ pub mod bson;
pub mod chrono;
#[cfg(feature = "chrono-tz")]
pub mod chrono_tz;
+#[cfg(feature = "jiff")]
+pub mod jiff;
#[cfg(feature = "rust_decimal")]
pub mod rust_decimal;
#[doc(hidden)]
diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs
index 84e178b98..63a3e245f 100644
--- a/juniper/src/integrations/time.rs
+++ b/juniper/src/integrations/time.rs
@@ -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(v: &Date) -> Value {
+ pub(super) fn to_output(v: &LocalDate) -> Value {
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(v: &InputValue) -> Result {
+ pub(super) fn from_input(v: &InputValue) -> Result {
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}"))
+ })
}
}
@@ -80,7 +83,11 @@ mod date {
///
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
/// [2]: https://docs.rs/time/*/time/struct.Time.html
-#[graphql_scalar(with = local_time, parse_token(String))]
+#[graphql_scalar(
+ with = local_time,
+ parse_token(String),
+ specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time",
+)]
pub type LocalTime = time::Time;
mod local_time {
@@ -129,20 +136,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(v: &LocalDateTime) -> Value {
Value::scalar(
@@ -243,12 +259,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 +273,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 +301,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 +408,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 +431,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 +460,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 +647,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 +655,7 @@ mod integration_test {
#[graphql_object]
impl Root {
- fn date() -> Date {
+ fn local_date() -> LocalDate {
date!(2015 - 03 - 14)
}
@@ -660,7 +677,7 @@ mod integration_test {
}
const DOC: &str = r#"{
- date
+ localDate
localTime
localDateTime
dateTime,
@@ -677,9 +694,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",
}),
diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs
index 6121ac56c..e7d9053fe 100644
--- a/juniper/src/integrations/url.rs
+++ b/juniper/src/integrations/url.rs
@@ -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(v: &InputValue) -> Result {
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}")))
}
}
diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs
index 4fed7f5fa..727c8bc05 100644
--- a/juniper/src/integrations/uuid.rs
+++ b/juniper/src/integrations/uuid.rs
@@ -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(v: &InputValue) -> Result {
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}")))
}
}
diff --git a/juniper_actix/CHANGELOG.md b/juniper_actix/CHANGELOG.md
index e478340f4..d1207f463 100644
--- a/juniper_actix/CHANGELOG.md
+++ b/juniper_actix/CHANGELOG.md
@@ -60,7 +60,7 @@ See [old CHANGELOG](/../../blob/juniper_actix-v0.4.0/juniper_actix/CHANGELOG.md)
[`actix-ws` crate]: https://docs.rs/actix-ws
[`juniper` crate]: https://docs.rs/juniper
[`juniper_graphql_ws` crate]: https://docs.rs/juniper_graphql_ws
-[Semantic Versioning 2.0.0]: https://semver.org
[graphql-transport-ws]: https://github.com/enisdenjo/graphql-ws/blob/v5.14.0/PROTOCOL.md
[graphql-ws]: https://github.com/apollographql/subscriptions-transport-ws/blob/v0.11.0/PROTOCOL.md
[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
+[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_actix/README.md b/juniper_actix/README.md
index f778ef1d5..599a4fb1a 100644
--- a/juniper_actix/README.md
+++ b/juniper_actix/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_actix.svg?maxAge=2592000)](https://crates.io/crates/juniper_actix)
[![Documentation](https://docs.rs/juniper_actix/badge.svg)](https://docs.rs/juniper_actix)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_actix-v0.6.0/juniper_actix/CHANGELOG.md)
diff --git a/juniper_axum/CHANGELOG.md b/juniper_axum/CHANGELOG.md
index 60232e595..f9ef13355 100644
--- a/juniper_axum/CHANGELOG.md
+++ b/juniper_axum/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_axum` crate will be documented in this file
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.1.0] · 2024-03-20
[0.1.0]: /../../tree/juniper_axum-v0.1.0/juniper_axum
@@ -38,8 +49,9 @@ All user visible changes to `juniper_axum` crate will be documented in this file
[`juniper` crate]: https://docs.rs/juniper
[`juniper_graphql_ws` crate]: https://docs.rs/juniper_graphql_ws
[GraphiQL]: https://github.com/graphql/graphiql
+[graphql-transport-ws]: https://github.com/enisdenjo/graphql-ws/blob/v5.14.0/PROTOCOL.md
+[graphql-ws]: https://github.com/apollographql/subscriptions-transport-ws/blob/v0.11.0/PROTOCOL.md
[GraphQL]: http://graphql.org
[GraphQL Playground]: https://github.com/prisma/graphql-playground
-[Semantic Versioning 2.0.0]: https://semver.org
-[graphql-transport-ws]: https://github.com/enisdenjo/graphql-ws/blob/v5.14.0/PROTOCOL.md
-[graphql-ws]: https://github.com/apollographql/subscriptions-transport-ws/blob/v0.11.0/PROTOCOL.md
\ No newline at end of file
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
+[Semantic Versioning 2.0.0]: https://semver.org
\ No newline at end of file
diff --git a/juniper_axum/Cargo.toml b/juniper_axum/Cargo.toml
index 459a2c675..2fd11abae 100644
--- a/juniper_axum/Cargo.toml
+++ b/juniper_axum/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_axum"
version = "0.1.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "`juniper` GraphQL integration with `axum`."
license = "BSD-2-Clause"
authors = [
diff --git a/juniper_axum/README.md b/juniper_axum/README.md
index 54356c875..40800ba3b 100644
--- a/juniper_axum/README.md
+++ b/juniper_axum/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_axum.svg?maxAge=2592000)](https://crates.io/crates/juniper_axum)
[![Documentation](https://docs.rs/juniper_axum/badge.svg)](https://docs.rs/juniper_axum)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_axum-v0.1.0/juniper_axum/CHANGELOG.md)
diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md
index 26e4c2297..856dd5609 100644
--- a/juniper_codegen/CHANGELOG.md
+++ b/juniper_codegen/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_codegen` crate will be documented in this f
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.16.0] · 2024-03-20
[0.16.0]: /../../tree/juniper_codegen-v0.16.0/juniper_codegen
@@ -67,5 +78,6 @@ All user visible changes to `juniper_codegen` crate will be documented in this f
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml
index eebd589b0..0a9e00278 100644
--- a/juniper_codegen/Cargo.toml
+++ b/juniper_codegen/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_codegen"
version = "0.16.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "Code generation for `juniper` crate."
license = "BSD-2-Clause"
authors = [
@@ -28,7 +28,7 @@ syn = { version = "2.0", features = ["extra-traits", "full", "visit", "visit-mut
url = "2.0"
[dev-dependencies]
-derive_more = "0.99.8"
+derive_more = { version = "1.0", features = ["from"] }
futures = "0.3.22"
juniper = { path = "../juniper" }
serde = "1.0.122"
diff --git a/juniper_codegen/README.md b/juniper_codegen/README.md
index fe87aa3a2..741a34231 100644
--- a/juniper_codegen/README.md
+++ b/juniper_codegen/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_codegen.svg?maxAge=2592000)](https://crates.io/crates/juniper_codegen)
[![Documentation](https://docs.rs/juniper_codegen/badge.svg)](https://docs.rs/juniper_codegen)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_codegen-v0.16.0/juniper_codegen/CHANGELOG.md)
diff --git a/juniper_graphql_ws/CHANGELOG.md b/juniper_graphql_ws/CHANGELOG.md
index a3ddaf55b..d931fb747 100644
--- a/juniper_graphql_ws/CHANGELOG.md
+++ b/juniper_graphql_ws/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_graphql_ws` crate will be documented in thi
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.4.0] · 2024-03-20
[0.4.0]: /../../tree/juniper_graphql_ws-v0.4.0/juniper_graphql_ws
@@ -44,4 +55,5 @@ See [old CHANGELOG](/../../blob/juniper_graphql_ws-v0.3.0/juniper_graphql_ws/CHA
[`graphql-ws` npm package]: https://npmjs.com/package/graphql-ws
[`juniper` crate]: https://docs.rs/juniper
[`juniper_subscriptions` crate]: https://docs.rs/juniper_subscriptions
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_graphql_ws/Cargo.toml b/juniper_graphql_ws/Cargo.toml
index 66a087b0b..98a4032d6 100644
--- a/juniper_graphql_ws/Cargo.toml
+++ b/juniper_graphql_ws/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_graphql_ws"
version = "0.4.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "GraphQL over WebSocket Protocol implementations for `juniper` crate."
license = "BSD-2-Clause"
authors = [
diff --git a/juniper_graphql_ws/README.md b/juniper_graphql_ws/README.md
index 74f1075c5..cba6d743e 100644
--- a/juniper_graphql_ws/README.md
+++ b/juniper_graphql_ws/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_graphql_ws.svg?maxAge=2592000)](https://crates.io/crates/juniper_graphql_ws)
[![Documentation](https://docs.rs/juniper_graphql_ws/badge.svg)](https://docs.rs/juniper_graphql_ws)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_graphql_ws-v0.4.0/juniper_graphql_ws/CHANGELOG.md)
diff --git a/juniper_hyper/CHANGELOG.md b/juniper_hyper/CHANGELOG.md
index ab2b561e7..1fd5085ae 100644
--- a/juniper_hyper/CHANGELOG.md
+++ b/juniper_hyper/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_hyper` crate will be documented in this fil
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.9.0] · 2024-03-20
[0.9.0]: /../../tree/juniper_hyper-v0.9.0/juniper_hyper
@@ -33,4 +44,5 @@ See [old CHANGELOG](/../../blob/juniper_hyper-v0.8.0/juniper_hyper/CHANGELOG.md)
[`juniper` crate]: https://docs.rs/juniper
[`hyper` crate]: https://docs.rs/hyper
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml
index 2c3bae15e..0f5f28737 100644
--- a/juniper_hyper/Cargo.toml
+++ b/juniper_hyper/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_hyper"
version = "0.9.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "`juniper` GraphQL integration with `hyper`."
license = "BSD-2-Clause"
authors = [
diff --git a/juniper_hyper/README.md b/juniper_hyper/README.md
index 219fc5ff2..bf115fd45 100644
--- a/juniper_hyper/README.md
+++ b/juniper_hyper/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_hyper.svg?maxAge=2592000)](https://crates.io/crates/juniper_hyper)
[![Documentation](https://docs.rs/juniper_hyper/badge.svg)](https://docs.rs/juniper_hyper)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_hyper-v0.9.0/juniper_hyper/CHANGELOG.md)
diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md
index 94bcdf858..a3d0fef54 100644
--- a/juniper_rocket/CHANGELOG.md
+++ b/juniper_rocket/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_rocket` crate will be documented in this fi
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.9.0] · 2024-03-20
[0.9.0]: /../../tree/juniper_rocket-v0.9.0/juniper_rocket
@@ -40,4 +51,5 @@ See [old CHANGELOG](/../../blob/juniper_rocket-v0.8.2/juniper_rocket/CHANGELOG.m
[`juniper` crate]: https://docs.rs/juniper
[`rocket` crate]: https://docs.rs/rocket
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml
index cd6ca824b..0174b1695 100644
--- a/juniper_rocket/Cargo.toml
+++ b/juniper_rocket/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_rocket"
version = "0.9.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "`juniper` GraphQL integration with `rocket`."
license = "BSD-2-Clause"
authors = [
diff --git a/juniper_rocket/README.md b/juniper_rocket/README.md
index 7d423920e..85466ab97 100644
--- a/juniper_rocket/README.md
+++ b/juniper_rocket/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_rocket.svg?maxAge=2592000)](https://crates.io/crates/juniper_rocket)
[![Documentation](https://docs.rs/juniper_rocket/badge.svg)](https://docs.rs/juniper_rocket)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_rocket-v0.9.0/juniper_rocket/CHANGELOG.md)
diff --git a/juniper_subscriptions/CHANGELOG.md b/juniper_subscriptions/CHANGELOG.md
index b59b6c626..83a30166a 100644
--- a/juniper_subscriptions/CHANGELOG.md
+++ b/juniper_subscriptions/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_subscriptions` crate will be documented in
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.17.0] · 2024-03-20
[0.17.0]: /../../tree/juniper_subscriptions-v0.17.0/juniper_subscriptions
@@ -24,4 +35,5 @@ See [old CHANGELOG](/../../blob/juniper_subscriptions-v0.16.0/juniper_subscripti
[`juniper` crate]: https://docs.rs/juniper
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_subscriptions/Cargo.toml b/juniper_subscriptions/Cargo.toml
index ccab24a36..c33bd28ee 100644
--- a/juniper_subscriptions/Cargo.toml
+++ b/juniper_subscriptions/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_subscriptions"
version = "0.17.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "Juniper `SubscriptionCoordinator` and `SubscriptionConnection` implementations."
license = "BSD-2-Clause"
authors = ["nWacky "]
diff --git a/juniper_subscriptions/README.md b/juniper_subscriptions/README.md
index 38f1537c5..750f50232 100644
--- a/juniper_subscriptions/README.md
+++ b/juniper_subscriptions/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_subscriptions.svg?maxAge=2592000)](https://crates.io/crates/juniper_subscriptions)
[![Documentation](https://docs.rs/juniper_subscriptions/badge.svg)](https://docs.rs/juniper_subscriptions)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_subscriptions-v0.17.0/juniper_subscriptions/CHANGELOG.md)
diff --git a/juniper_warp/CHANGELOG.md b/juniper_warp/CHANGELOG.md
index a3ad52e26..fdc1513bd 100644
--- a/juniper_warp/CHANGELOG.md
+++ b/juniper_warp/CHANGELOG.md
@@ -6,6 +6,17 @@ All user visible changes to `juniper_warp` crate will be documented in this file
+## master
+
+### BC Breaks
+
+- Bumped up [MSRV] to 1.75. ([#1272])
+
+[#1272]: /../../pull/1272
+
+
+
+
## [0.8.0] · 2024-03-20
[0.8.0]: /../../tree/juniper_warp-v0.8.0/juniper_warp
@@ -46,6 +57,7 @@ See [old CHANGELOG](/../../blob/juniper_warp-v0.7.0/juniper_warp/CHANGELOG.md).
[`juniper` crate]: https://docs.rs/juniper
-[Semantic Versioning 2.0.0]: https://semver.org
[graphql-transport-ws]: https://github.com/enisdenjo/graphql-ws/blob/v5.14.0/PROTOCOL.md
[graphql-ws]: https://github.com/apollographql/subscriptions-transport-ws/blob/v0.11.0/PROTOCOL.md
+[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field
+[Semantic Versioning 2.0.0]: https://semver.org
diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml
index a93aa976e..1d1c5f0d9 100644
--- a/juniper_warp/Cargo.toml
+++ b/juniper_warp/Cargo.toml
@@ -2,7 +2,7 @@
name = "juniper_warp"
version = "0.8.0"
edition = "2021"
-rust-version = "1.73"
+rust-version = "1.75"
description = "`juniper` GraphQL integration with `warp`."
license = "BSD-2-Clause"
authors = [
diff --git a/juniper_warp/README.md b/juniper_warp/README.md
index e08231ff9..c3a61bd6e 100644
--- a/juniper_warp/README.md
+++ b/juniper_warp/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_warp.svg?maxAge=2592000)](https://crates.io/crates/juniper_warp)
[![Documentation](https://docs.rs/juniper_warp/badge.svg)](https://docs.rs/juniper_warp)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
-[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html)
+[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_warp-v0.8.0/juniper_warp/CHANGELOG.md)
diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml
index 26139c5d7..5e8fd63e5 100644
--- a/tests/integration/Cargo.toml
+++ b/tests/integration/Cargo.toml
@@ -7,7 +7,7 @@ publish = false
[dev-dependencies]
async-trait = "0.1.39"
chrono = { version = "0.4", default-features = false }
-derive_more = "0.99"
+derive_more = { version = "1.0", features = ["from"] }
fnv = "1.0"
futures = "0.3"
itertools = "0.13"