Skip to content

Commit c98b6d5

Browse files
committed
Strip potential redundant allocations from jiff and time GraphQLScalar implementations
1 parent ffe8064 commit c98b6d5

File tree

4 files changed

+151
-51
lines changed

4 files changed

+151
-51
lines changed

juniper/src/integrations/jiff.rs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ use crate::{ScalarValue, graphql_scalar};
7777
pub type LocalDate = jiff::civil::Date;
7878

7979
mod local_date {
80-
use super::*;
80+
use std::fmt::Display;
81+
82+
use super::LocalDate;
8183

8284
/// Format of a [`LocalDate` scalar][1].
8385
///
@@ -114,7 +116,9 @@ mod local_date {
114116
pub type LocalTime = jiff::civil::Time;
115117

116118
mod local_time {
117-
use super::*;
119+
use std::fmt::Display;
120+
121+
use super::LocalTime;
118122

119123
/// Full format of a [`LocalTime` scalar][1].
120124
///
@@ -172,7 +176,9 @@ mod local_time {
172176
pub type LocalDateTime = jiff::civil::DateTime;
173177

174178
mod local_date_time {
175-
use super::*;
179+
use std::fmt::Display;
180+
181+
use super::LocalDateTime;
176182

177183
/// Format of a [`LocalDateTime` scalar][1].
178184
///
@@ -208,7 +214,9 @@ mod local_date_time {
208214
pub type DateTime = jiff::Timestamp;
209215

210216
mod date_time {
211-
use super::*;
217+
use std::fmt::Display;
218+
219+
use super::DateTime;
212220

213221
/// Format of a [`DateTime` scalar][1].
214222
///
@@ -310,24 +318,27 @@ mod duration {
310318
pub type TimeZoneOrUtcOffset = jiff::tz::TimeZone;
311319

312320
mod time_zone_or_utc_offset {
313-
use super::*;
321+
use std::fmt::Display;
322+
323+
use super::{TimeZoneOrUtcOffset, TimeZoneParsingError, utc_offset};
324+
use crate::util::Either;
314325

315326
/// Format of a [`TimeZoneOrUtcOffset`] scalar.
316327
const FORMAT: &str = "%:Q";
317328

318-
pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> String {
319-
v.iana_name().map_or_else(
320-
|| {
321-
// If no IANA time zone identifier is available, fall back to displaying the time
322-
// offset directly (using format `[+-]HH:MM[:SS]` from RFC 9557, e.g. `+05:30`).
323-
// See: https://github.com/graphql-rust/juniper/pull/1278#discussion_r1719161686
329+
pub(super) fn to_output(v: &TimeZoneOrUtcOffset) -> impl Display {
330+
if let Some(name) = v.iana_name() {
331+
Either::Left(name)
332+
} else {
333+
// If no IANA time zone identifier is available, fall back to displaying the time
334+
// offset directly (using format `[+-]HH:MM[:SS]` from RFC 9557, e.g. `+05:30`).
335+
// See: https://github.com/graphql-rust/juniper/pull/1278#discussion_r1719161686
336+
Either::Right(
324337
jiff::Zoned::now()
325338
.with_time_zone(v.clone())
326-
.strftime(FORMAT)
327-
.to_string()
328-
},
329-
ToOwned::to_owned,
330-
)
339+
.strftime(FORMAT),
340+
)
341+
}
331342
}
332343

333344
pub(super) fn from_input(s: &str) -> Result<TimeZoneOrUtcOffset, Box<str>> {
@@ -424,28 +435,39 @@ mod time_zone {
424435
pub type UtcOffset = jiff::tz::Offset;
425436

426437
mod utc_offset {
427-
use super::*;
438+
use std::fmt::{self, Display};
439+
440+
use jiff::fmt::{StdFmtWrite, strtime::BrokenDownTime};
441+
442+
use super::UtcOffset;
428443

429444
/// Format of a [`UtcOffset` scalar][1].
430445
///
431446
/// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset
432447
const FORMAT: &str = "%:z";
433448

434449
pub(super) fn utc_offset_from_str(value: &str) -> Result<jiff::tz::Offset, jiff::Error> {
435-
let tm = jiff::fmt::strtime::BrokenDownTime::parse(FORMAT, value)?;
450+
let tm = BrokenDownTime::parse(FORMAT, value)?;
436451
let offset = tm
437452
.offset()
438453
.expect("successful %:z parsing guarantees offset");
439454
Ok(offset)
440455
}
441456

442-
pub(super) fn to_output(v: &UtcOffset) -> String {
443-
let mut buf = String::new();
444-
let tm = jiff::fmt::strtime::BrokenDownTime::from(
457+
pub(super) fn to_output(v: &UtcOffset) -> impl Display {
458+
struct LazyFmt(BrokenDownTime);
459+
460+
impl Display for LazyFmt {
461+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462+
self.0
463+
.format(FORMAT, StdFmtWrite(f))
464+
.map_err(|_| fmt::Error)
465+
}
466+
}
467+
468+
LazyFmt(BrokenDownTime::from(
445469
&jiff::Zoned::now().with_time_zone(jiff::tz::TimeZone::fixed(*v)),
446-
);
447-
tm.format(FORMAT, &mut buf).unwrap();
448-
buf
470+
))
449471
}
450472

451473
pub(super) fn from_input(s: &str) -> Result<UtcOffset, Box<str>> {

juniper/src/integrations/time.rs

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
//! [s4]: https://graphql-scalars.dev/docs/scalars/date-time
2323
//! [s5]: https://graphql-scalars.dev/docs/scalars/utc-offset
2424
25+
use std::{
26+
fmt::{self, Display},
27+
io, str,
28+
};
2529
use time::{
2630
format_description::{BorrowedFormatItem, well_known::Rfc3339},
2731
macros::format_description,
@@ -56,9 +60,17 @@ mod local_date {
5660
/// [1]: https://graphql-scalars.dev/docs/scalars/local-date
5761
const FORMAT: &[BorrowedFormatItem<'_>] = format_description!("[year]-[month]-[day]");
5862

59-
pub(super) fn to_output(v: &LocalDate) -> String {
60-
v.format(FORMAT)
61-
.unwrap_or_else(|e| panic!("failed to format `LocalDate`: {e}"))
63+
impl Display for LazyFmt<&LocalDate> {
64+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65+
self.0
66+
.format_into(&mut IoAdapter(f), FORMAT)
67+
.map_err(|_| fmt::Error)
68+
.map(drop)
69+
}
70+
}
71+
72+
pub(super) fn to_output(v: &LocalDate) -> impl Display {
73+
LazyFmt(v)
6274
}
6375

6476
pub(super) fn from_input(s: &str) -> Result<LocalDate, Box<str>> {
@@ -106,13 +118,24 @@ mod local_time {
106118
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
107119
const FORMAT_NO_SECS: &[BorrowedFormatItem<'_>] = format_description!("[hour]:[minute]");
108120

109-
pub(super) fn to_output(v: &LocalTime) -> String {
110-
if v.millisecond() == 0 {
111-
v.format(FORMAT_NO_MILLIS)
112-
} else {
113-
v.format(FORMAT)
121+
impl Display for LazyFmt<&LocalTime> {
122+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123+
self.0
124+
.format_into(
125+
&mut IoAdapter(f),
126+
if self.0.millisecond() == 0 {
127+
FORMAT_NO_MILLIS
128+
} else {
129+
FORMAT
130+
},
131+
)
132+
.map_err(|_| fmt::Error)
133+
.map(drop)
114134
}
115-
.unwrap_or_else(|e| panic!("failed to format `LocalTime`: {e}"))
135+
}
136+
137+
pub(super) fn to_output(v: &LocalTime) -> impl Display {
138+
LazyFmt(v)
116139
}
117140

118141
pub(super) fn from_input(s: &str) -> Result<LocalTime, Box<str>> {
@@ -150,9 +173,17 @@ mod local_date_time {
150173
const FORMAT: &[BorrowedFormatItem<'_>] =
151174
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
152175

153-
pub(super) fn to_output(v: &LocalDateTime) -> String {
154-
v.format(FORMAT)
155-
.unwrap_or_else(|e| panic!("failed to format `LocalDateTime`: {e}"))
176+
impl Display for LazyFmt<&LocalDateTime> {
177+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178+
self.0
179+
.format_into(&mut IoAdapter(f), FORMAT)
180+
.map_err(|_| fmt::Error)
181+
.map(drop)
182+
}
183+
}
184+
185+
pub(super) fn to_output(v: &LocalDateTime) -> impl Display {
186+
LazyFmt(v)
156187
}
157188

158189
pub(super) fn from_input(s: &str) -> Result<LocalDateTime, Box<str>> {
@@ -183,10 +214,18 @@ pub type DateTime = time::OffsetDateTime;
183214
mod date_time {
184215
use super::*;
185216

186-
pub(super) fn to_output(v: &DateTime) -> String {
187-
v.to_offset(UtcOffset::UTC)
188-
.format(&Rfc3339)
189-
.unwrap_or_else(|e| panic!("failed to format `DateTime`: {e}"))
217+
impl Display for LazyFmt<&DateTime> {
218+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219+
self.0
220+
.to_offset(UtcOffset::UTC)
221+
.format_into(&mut IoAdapter(f), &Rfc3339)
222+
.map_err(|_| fmt::Error)
223+
.map(drop)
224+
}
225+
}
226+
227+
pub(super) fn to_output(v: &DateTime) -> impl Display {
228+
LazyFmt(v)
190229
}
191230

192231
pub(super) fn from_input(s: &str) -> Result<DateTime, Box<str>> {
@@ -222,9 +261,17 @@ pub type UtcOffset = time::UtcOffset;
222261
mod utc_offset {
223262
use super::*;
224263

225-
pub(super) fn to_output(v: &UtcOffset) -> String {
226-
v.format(UTC_OFFSET_FORMAT)
227-
.unwrap_or_else(|e| panic!("failed to format `UtcOffset`: {e}"))
264+
impl Display for LazyFmt<&UtcOffset> {
265+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266+
self.0
267+
.format_into(&mut IoAdapter(f), UTC_OFFSET_FORMAT)
268+
.map_err(|_| fmt::Error)
269+
.map(drop)
270+
}
271+
}
272+
273+
pub(super) fn to_output(v: &UtcOffset) -> impl Display {
274+
LazyFmt(v)
228275
}
229276

230277
pub(super) fn from_input(s: &str) -> Result<UtcOffset, Box<str>> {
@@ -233,6 +280,30 @@ mod utc_offset {
233280
}
234281
}
235282

283+
// TODO: Remove once time-rs/time#375 is resolved:
284+
// https://github.com/time-rs/time/issues/375
285+
/// [`io::Write`] adapter for [`fmt::Formatter`].
286+
///
287+
/// Required because [`time`] crate cannot write to [`fmt::Write`], only to [`io::Write`].
288+
struct IoAdapter<'a, 'b>(&'a mut fmt::Formatter<'b>);
289+
290+
impl io::Write for IoAdapter<'_, '_> {
291+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
292+
let s = str::from_utf8(buf).map_err(io::Error::other)?;
293+
match self.0.write_str(s) {
294+
Ok(_) => Ok(s.len()),
295+
Err(e) => Err(io::Error::other(e)),
296+
}
297+
}
298+
299+
fn flush(&mut self) -> io::Result<()> {
300+
Ok(())
301+
}
302+
}
303+
304+
/// Wrapper over [`time`] crate types to [`Display`] their format lazily.
305+
struct LazyFmt<T>(T);
306+
236307
#[cfg(test)]
237308
mod local_date_test {
238309
use time::macros::date;

juniper/src/util.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::borrow::Cow;
22

3+
use derive_more::with_trait::Display;
4+
35
/// Convert string to camel case.
46
///
57
/// Note: needs to be public because several macros use it.
@@ -51,3 +53,13 @@ fn test_to_camel_case() {
5153
assert_eq!(&to_camel_case("a")[..], "a");
5254
assert_eq!(&to_camel_case("")[..], "");
5355
}
56+
57+
/// Combination of two [`Display`]able types into a single one.
58+
#[derive(Display)]
59+
pub(crate) enum Either<L, R> {
60+
/// Left value of the first type.
61+
Left(L),
62+
63+
/// Right value of the second type.
64+
Right(R),
65+
}

juniper/src/validation/rules/overlapping_fields_can_be_merged.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
ast::{Arguments, Definition, Document, Field, Fragment, FragmentSpread, Selection, Type},
99
parser::{SourcePosition, Spanning},
1010
schema::meta::{Field as FieldType, MetaType},
11+
util::Either,
1112
validation::{ValidatorContext, Visitor},
1213
value::ScalarValue,
1314
};
@@ -748,15 +749,9 @@ fn error_message(reason_name: &str, reason: &ConflictReasonMessage) -> String {
748749
}
749750

750751
fn format_reason(reason: &ConflictReasonMessage) -> impl Display {
751-
#[derive(Display)]
752-
enum Either<A, B> {
753-
A(A),
754-
B(B),
755-
}
756-
757752
match reason {
758-
ConflictReasonMessage::Message(name) => Either::A(name),
759-
ConflictReasonMessage::Nested(nested) => Either::B(nested.iter().format_with(
753+
ConflictReasonMessage::Message(name) => Either::Left(name),
754+
ConflictReasonMessage::Nested(nested) => Either::Right(nested.iter().format_with(
760755
" and ",
761756
|ConflictReason(name, subreason), f| {
762757
f(&format_args!(

0 commit comments

Comments
 (0)