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"