Skip to content

Commit 1027105

Browse files
committed
Polish jiff crate integration (#1278, #1270)
1 parent 834b737 commit 1027105

File tree

3 files changed

+72
-69
lines changed

3 files changed

+72
-69
lines changed

book/src/types/scalars.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ mod date_scalar {
402402
| [`jiff::Timestamp`] | [`DateTime`] | [`jiff`] |
403403
| [`jiff::Zoned`] | `ZonedDateTime` | [`jiff`] |
404404
| [`jiff::tz::TimeZone`] | `TimeZoneOrUtcOffset` | [`jiff`] |
405-
| [`jiff::tz::TimeZone`] | [`TimeZone`] [^n1] | [`jiff`] |
405+
| [`jiff::tz::TimeZone`] via [`juniper::integrations::jiff::TimeZone`] | [`TimeZone`] | [`jiff`] |
406406
| [`jiff::tz::Offset`] | [`UtcOffset`] | [`jiff`] |
407407
| [`jiff::Span`] | [`Duration`] | [`jiff`] |
408408
| [`time::Date`] | [`LocalDate`] | [`time`] |
@@ -413,8 +413,6 @@ mod date_scalar {
413413
| [`url::Url`] | [`URL`] | [`url`] |
414414
| [`uuid::Uuid`] | [`UUID`] | [`uuid`] |
415415

416-
[^n1]: Conversion supported via newtype [`integrations::jiff::TimeZone`][10].
417-
418416

419417

420418

@@ -442,6 +440,7 @@ mod date_scalar {
442440
[`jiff::tz::Offset`]: https://docs.rs/jiff/latest/jiff/tz/struct.Offset.html
443441
[`jiff::tz::TimeZone`]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html
444442
[`jiff::Zoned`]: https://docs.rs/jiff/latest/jiff/struct.Zoned.html
443+
[`juniper::integrations::jiff::TimeZone`]: https://docs.rs/juniper/0.16.1/juniper/integrations/jiff/struct.TimeZone.html
445444
[`LocalDate`]: https://graphql-scalars.dev/docs/scalars/local-date
446445
[`LocalDateTime`]: https://graphql-scalars.dev/docs/scalars/local-date-time
447446
[`LocalTime`]: https://graphql-scalars.dev/docs/scalars/local-time
@@ -481,4 +480,3 @@ mod date_scalar {
481480
[7]: https://spec.graphql.org/October2021#sec-Value-Resolution
482481
[8]: https://docs.rs/juniper/0.16.1/juniper/derive.GraphQLScalar.html
483482
[9]: https://docs.rs/juniper/0.16.1/juniper/attr.graphql_scalar.html
484-
[10]: https://docs.rs/juniper/0.16.1/juniper/integrations/jiff/struct.TimeZone.html

juniper/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
4040
- `jiff::civil::DateTime` as `LocalDateTime` scalar. ([#1275])
4141
- `jiff::Timestamp` as `DateTime` scalar.
4242
- `jiff::Zoned` as `ZonedDateTime` scalar.
43-
- `jiff::tz::TimeZone` as `TimeZoneOrUtcOffset` scalar.
43+
- `jiff::tz::TimeZone` as `TimeZoneOrUtcOffset` and `TimeZone` scalars.
4444
- `jiff::tz::Offset` as `UtcOffset` scalar.
4545
- `jiff::Span` as `Duration` scalar.
4646

juniper/src/integrations/jiff.rs

Lines changed: 69 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@
22
//!
33
//! # Supported types
44
//!
5-
//! | Rust type | Format | GraphQL scalar |
6-
//! |---------------------------------------|-----------------------------|-----------------------|
7-
//! | [`civil::Date`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
8-
//! | [`civil::Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
9-
//! | [`civil::DateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
10-
//! | [`Timestamp`] | [RFC 3339] string | [`DateTime`][s4] |
11-
//! | [`Zoned`][^1] | [RFC 9557] string | `ZonedDateTime` |
12-
//! | [`tz::TimeZone`][^1] | [IANA database][1]/`±hh:mm` | `TimeZoneOrUtcOffset` |
13-
//! | [`tz::TimeZone`] via [`TimeZone`][^1] | [IANA database][1] | [`TimeZone`][s5] |
14-
//! | [`tz::Offset`] | `±hh:mm` | [`UtcOffset`][s6] |
15-
//! | [`Span`] | [ISO 8601] duration | [`Duration`][s7] |
5+
//! | Rust type | Format | GraphQL scalar |
6+
//! |----------------------------------------|----------------------------|-----------------------|
7+
//! | [`civil::Date`] | `yyyy-MM-dd` | [`LocalDate`][s1] |
8+
//! | [`civil::Time`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] |
9+
//! | [`civil::DateTime`] | `yyyy-MM-ddTHH:mm:ss` | [`LocalDateTime`][s3] |
10+
//! | [`Timestamp`] | [RFC 3339] string | [`DateTime`][s4] |
11+
//! | [`Zoned`] [^1] | [RFC 9557] string | `ZonedDateTime` |
12+
//! | [`tz::TimeZone`] [^1] | [IANA] identifier/`±hh:mm` | `TimeZoneOrUtcOffset` |
13+
//! | [`tz::TimeZone`] via [`TimeZone`] [^1] | [IANA] identifier | [`TimeZone`][s5] |
14+
//! | [`tz::Offset`] | `±hh:mm` | [`UtcOffset`][s6] |
15+
//! | [`Span`] | [ISO 8601] duration | [`Duration`][s7] |
1616
//!
17-
//! [^1]: For these, crate [`jiff`] must be installed with a feature flag that provides access to
18-
//! the Time Zone Database (e.g. by using the crate's default feature flags). See [`jiff` time zone
19-
//! features][tz] for details.
17+
//! # [`tz::TimeZone`] types
2018
//!
21-
//! # Time zone types
19+
//! [`tz::TimeZone`] values can be either [IANA] identifiers or fixed offsets, corresponding to
20+
//! GraphQL scalars [`TimeZone`][s5] and [`UtcOffset`][s6] accordingly. While a [`UtcOffset`][s6]
21+
//! GraphQL scalar can be serialized from a [`tz::Offset`] directly, the newtype [`TimeZone`]
22+
//! handles serialization to a [`TimeZone`][s5] GraphQL scalar, with implementations [`TryFrom`] and
23+
//! [`Into`] a [`tz::TimeZone`].
2224
//!
23-
//! `tz::TimeZone` values can be IANA time zone identifiers or fixed offsets, corresponding to
24-
//! GraphQL scalars [`TimeZone`][s5] and [`UtcOffset`][s6]. While `UtcOffset` can be serialized from
25-
//! [`tz::Offset`] directly, newtype [`TimeZone`] handles serialization to `TimeZone`, with
26-
//! [`TryFrom`] and [`Into`] implementations from and to `tz::TimeZone`.
25+
//! In addition, a [`tz::TimeZone`] serializes to a `TimeZoneOrUtcOffset` GraphQL scalar, containing
26+
//! either an [IANA] identifier or a fixed offset for clients being able to consume both values.
2727
//!
28-
//! In addition, `tz::TimeZone` serializes to `TimeZoneOrUtcOffset` which is a GraphQL scalar that
29-
//! contains either an IANA identifier or a fixed offset for clients that can consume both values.
28+
//! [^1]: For these, crate [`jiff`] must be installed with a feature flag that provides access to
29+
//! the [IANA Time Zone Database][IANA] (e.g. by using the crate's default feature flags).
30+
//! See [`jiff` time zone features][1] for details.
3031
//!
3132
//! [`civil::Date`]: jiff::civil::Date
3233
//! [`civil::DateTime`]: jiff::civil::DateTime
@@ -36,6 +37,7 @@
3637
//! [`tz::Offset`]: jiff::tz::Offset
3738
//! [`tz::TimeZone`]: jiff::tz::TimeZone
3839
//! [`Zoned`]: jiff::Zoned
40+
//! [IANA]: http://iana.org/time-zones
3941
//! [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations
4042
//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
4143
//! [RFC 9557]: https://datatracker.ietf.org/doc/html/rfc9557#section-4.1
@@ -46,8 +48,7 @@
4648
//! [s5]: https://graphql-scalars.dev/docs/scalars/time-zone
4749
//! [s6]: https://graphql-scalars.dev/docs/scalars/utc-offset
4850
//! [s7]: https://graphql-scalars.dev/docs/scalars/duration
49-
//! [tz]: https://docs.rs/jiff/latest/jiff/index.html#time-zone-features
50-
//! [1]: http://www.iana.org/time-zones
51+
//! [1]: https://docs.rs/jiff/latest/jiff/index.html#time-zone-features
5152
5253
use std::{error::Error, fmt, str};
5354

@@ -264,7 +265,6 @@ mod date_time {
264265
/// Time zone aware instant in time.
265266
///
266267
/// Can be thought of as combination of the following types, all rolled into one:
267-
///
268268
/// - [`Timestamp`][3] for indicating precise instant in time.
269269
/// - [`DateTime`][4] for indicating "civil" calendar date and clock time.
270270
/// - [`TimeZone`][5] for indicating how to apply time zone transitions while performing arithmetic.
@@ -281,6 +281,7 @@ mod date_time {
281281
#[graphql_scalar(
282282
with = zoned_date_time,
283283
parse_token(String),
284+
specified_by_url = "https://datatracker.ietf.org/doc/html/rfc9557#section-4.1",
284285
)]
285286
pub type ZonedDateTime = jiff::Zoned;
286287

@@ -348,14 +349,20 @@ mod duration {
348349
}
349350
}
350351

351-
/// Representation of time zone or UTC offset.
352+
/// Representation of a time zone or UTC offset.
352353
///
353-
/// [IANA database][1] or `±hh:mm`.
354+
/// Can be one of three possible representations:
355+
/// - Identifier from the [IANA Time Zone Database][0].
356+
/// - Fixed offset from UTC (`±hh:mm`).
357+
///
358+
/// May be seen as a combination of both [`TimeZone`][3] and [`UtcOffset` scalars][4].
354359
///
355360
/// See also [`jiff::tz::TimeZone`][2] for details.
356361
///
357-
/// [1]: http://www.iana.org/time-zones
362+
/// [0]: http://iana.org/time-zones
358363
/// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html
364+
/// [3]: https://graphql-scalars.dev/docs/scalars/time-zone
365+
/// [4]: https://graphql-scalars.dev/docs/scalars/utc-offset
359366
#[graphql_scalar(
360367
with = time_zone_or_utc_offset,
361368
parse_token(String),
@@ -365,7 +372,7 @@ pub type TimeZoneOrUtcOffset = jiff::tz::TimeZone;
365372
mod time_zone_or_utc_offset {
366373
use super::*;
367374

368-
/// Format of a `TimeZoneOrUtcOffset` scalar.
375+
/// Format of a [`TimeZoneOrUtcOffset`] scalar.
369376
const FORMAT: &str = "%:V";
370377

371378
pub(super) fn to_output<S>(v: &TimeZoneOrUtcOffset) -> Value<S>
@@ -376,8 +383,7 @@ mod time_zone_or_utc_offset {
376383
|| {
377384
// If no IANA time zone identifier is available, fall back to displaying the time
378385
// offset directly (using format `[+-]HH:MM[:SS]` from RFC 9557, e.g. `+05:30`).
379-
//
380-
// <https://github.com/graphql-rust/juniper/pull/1278#discussion_r1719161686>
386+
// See: https://github.com/graphql-rust/juniper/pull/1278#discussion_r1719161686
381387
jiff::Zoned::now()
382388
.with_time_zone(v.clone())
383389
.strftime(FORMAT)
@@ -395,50 +401,51 @@ mod time_zone_or_utc_offset {
395401
.ok_or_else(|| format!("Expected `String`, found: {v}"))
396402
.and_then(|s| {
397403
TimeZoneOrUtcOffset::get(s)
398-
.map_err(TimeZoneError::InvalidTimeZone)
404+
.map_err(TimeZoneParsingError::InvalidTimeZone)
399405
.or_else(|_| utc_offset::utc_offset_from_str(s).map(TimeZoneOrUtcOffset::fixed))
400406
.map_err(|e| format!("Invalid `TimeZoneOrUtcOffset`: {e}"))
401407
})
402408
}
403409
}
404410

405-
/// Error while handling [`TimeZone`] value.
411+
/// Error parsing a [`TimeZone`] value.
406412
#[derive(Clone)]
407-
pub enum TimeZoneError {
408-
/// Identifier could not be parsed by [`tz::TimeZone::get`](jiff::tz::TimeZone::get).
413+
pub enum TimeZoneParsingError {
414+
/// Identifier cannot not be parsed by the [`jiff::tz::TimeZone::get()`] method.
409415
InvalidTimeZone(jiff::Error),
416+
410417
/// GraphQL scalar [`TimeZone`] requires `tz::TimeZone` with IANA name.
411418
MissingIanaName(jiff::tz::TimeZone),
412419
}
413420

414-
impl fmt::Debug for TimeZoneError {
421+
impl fmt::Debug for TimeZoneParsingError {
415422
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416423
match self {
417-
Self::InvalidTimeZone(err) => write!(f, "TimeZoneError::InvalidTimeZone({err:?})"),
418-
Self::MissingIanaName(_value) => write!(f, "TimeZoneError::MissingIanaName(..)"),
424+
Self::InvalidTimeZone(e) => write!(f, "TimeZoneParsingError::InvalidTimeZone({e:?})"),
425+
Self::MissingIanaName(_) => write!(f, "TimeZoneParsingError::MissingIanaName(..)"),
419426
}
420427
}
421428
}
422429

423-
impl fmt::Display for TimeZoneError {
430+
impl fmt::Display for TimeZoneParsingError {
424431
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425432
match self {
426-
Self::InvalidTimeZone(err) => err.fmt(f),
427-
Self::MissingIanaName(_value) => write!(f, "missing IANA name"),
433+
Self::InvalidTimeZone(e) => e.fmt(f),
434+
Self::MissingIanaName(..) => write!(f, "missing IANA name"),
428435
}
429436
}
430437
}
431438

432-
impl Error for TimeZoneError {
439+
impl Error for TimeZoneParsingError {
433440
fn source(&self) -> Option<&(dyn Error + 'static)> {
434441
match self {
435-
Self::InvalidTimeZone(err) => Some(err),
436-
Self::MissingIanaName(_) => None,
442+
Self::InvalidTimeZone(e) => Some(e),
443+
Self::MissingIanaName(..) => None,
437444
}
438445
}
439446
}
440447

441-
/// Representation of time zone.
448+
/// Representation of a time zone from the [IANA Time Zone Database][0].
442449
///
443450
/// A set of rules for determining the civil time, via an offset from UTC, in a particular
444451
/// geographic region. In many cases, the offset in a particular time zone can vary over the course
@@ -448,6 +455,7 @@ impl Error for TimeZoneError {
448455
///
449456
/// See also [`jiff::tz::TimeZone`][2] for details.
450457
///
458+
/// [0]: http://iana.org/time-zones
451459
/// [1]: https://graphql-scalars.dev/docs/scalars/time-zone
452460
/// [2]: https://docs.rs/jiff/latest/jiff/tz/struct.TimeZone.html
453461
#[graphql_scalar(
@@ -459,21 +467,22 @@ impl Error for TimeZoneError {
459467
pub struct TimeZone(jiff::tz::TimeZone);
460468

461469
impl TryFrom<jiff::tz::TimeZone> for TimeZone {
462-
type Error = TimeZoneError;
470+
type Error = TimeZoneParsingError;
463471

464472
fn try_from(value: jiff::tz::TimeZone) -> Result<Self, Self::Error> {
465473
if value.iana_name().is_none() {
466-
return Err(TimeZoneError::MissingIanaName(value));
474+
return Err(TimeZoneParsingError::MissingIanaName(value));
467475
}
468476
Ok(Self(value))
469477
}
470478
}
471479

472480
impl str::FromStr for TimeZone {
473-
type Err = TimeZoneError;
481+
type Err = TimeZoneParsingError;
474482

475483
fn from_str(value: &str) -> Result<Self, Self::Err> {
476-
let value = jiff::tz::TimeZone::get(value).map_err(TimeZoneError::InvalidTimeZone)?;
484+
let value =
485+
jiff::tz::TimeZone::get(value).map_err(TimeZoneParsingError::InvalidTimeZone)?;
477486
value.try_into()
478487
}
479488
}
@@ -482,8 +491,10 @@ impl fmt::Display for TimeZone {
482491
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483492
self.0
484493
.iana_name()
485-
// PANIC: We made sure that IANA name is available when constructing `Self`.
486-
.unwrap_or_else(|| panic!("Failed to display `TimeZone`: no IANA name"))
494+
.unwrap_or_else(|| {
495+
// PANIC: We made sure that IANA name is available when constructing `Self`.
496+
panic!("failed to display `TimeZone`: no IANA name")
497+
})
487498
.fmt(f)
488499
}
489500
}
@@ -495,8 +506,6 @@ impl From<TimeZone> for jiff::tz::TimeZone {
495506
}
496507

497508
mod time_zone {
498-
use std::str::FromStr as _;
499-
500509
use super::*;
501510

502511
pub(super) fn to_output<S>(v: &TimeZone) -> Value<S>
@@ -512,11 +521,11 @@ mod time_zone {
512521
{
513522
v.as_string_value()
514523
.ok_or_else(|| format!("Expected `String`, found: {v}"))
515-
.and_then(|s| TimeZone::from_str(s).map_err(|e| format!("Invalid `TimeZone`: {e}")))
524+
.and_then(|s| s.parse().map_err(|e| format!("Invalid `TimeZone`: {e}")))
516525
}
517526
}
518527

519-
/// Represents fixed time zone offset.
528+
/// Representation of a fixed time zone offset.
520529
///
521530
/// [`UtcOffset` scalar][1] compliant.
522531
///
@@ -547,20 +556,16 @@ mod utc_offset {
547556
Ok(offset)
548557
}
549558

550-
fn utc_offset_to_string(value: jiff::tz::Offset) -> String {
551-
let mut buf = String::new();
552-
let tm = jiff::fmt::strtime::BrokenDownTime::from(
553-
&jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(value)),
554-
);
555-
tm.format(FORMAT, &mut buf).unwrap();
556-
buf
557-
}
558-
559559
pub(super) fn to_output<S>(v: &UtcOffset) -> Value<S>
560560
where
561561
S: ScalarValue,
562562
{
563-
Value::scalar(utc_offset_to_string(*v))
563+
let mut buf = String::new();
564+
let tm = jiff::fmt::strtime::BrokenDownTime::from(
565+
&jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(*v)),
566+
);
567+
tm.format(FORMAT, &mut buf).unwrap();
568+
Value::scalar(buf)
564569
}
565570

566571
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<UtcOffset, String>

0 commit comments

Comments
 (0)