Skip to content

Commit 78d8f15

Browse files
committed
chore: add jiff-0_2
1 parent 296c1e6 commit 78d8f15

18 files changed

+749
-31
lines changed

Cargo.lock

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ version = "7.0.17"
1515

1616
[features]
1717
apollo_persisted_queries = ["lru", "sha2"]
18-
apollo_tracing = ["chrono"]
18+
apollo_tracing = []
1919
email-validator = ["fast_chemail"]
2020
cbor = ["serde_cbor"]
2121
chrono-duration = ["chrono", "iso8601"]
22+
jiff-0_2 = ["dep:jiff"]
23+
jiff-tz = ["jiff-0_2", "dep:jiff-tzdb"]
2224
dataloader = ["futures-channel", "lru"]
2325
decimal = ["rust_decimal"]
24-
default = ["email-validator", "tempfile", "playground", "graphiql"]
26+
default = ["email-validator", "tempfile", "playground", "graphiql", "chrono"]
2527
password-strength-validator = ["zxcvbn"]
2628
string_number = []
2729
tokio-sync = ["tokio"]
@@ -81,6 +83,10 @@ chrono = { version = "0.4.37", optional = true, default-features = false, featur
8183
"std",
8284
] }
8385
chrono-tz = { version = "0.10.0", optional = true }
86+
jiff = { version = "0.2", optional = true, default-features = false, features = [
87+
"std",
88+
] }
89+
jiff-tzdb = { version = "0.1", optional = true }
8490
fast_chemail = { version = "0.9.6", optional = true }
8591
hashbrown = { version = "0.16", optional = true }
8692
iso8601 = { version = "0.6.1", optional = true }

src/extensions/apollo_tracing.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use std::sync::Arc;
22

3-
use chrono::{DateTime, Utc};
43
use futures_util::lock::Mutex;
54
use serde::{Serialize, Serializer, ser::SerializeMap};
65

6+
// Timestamp abstraction to support both chrono and jiff
7+
#[cfg(all(feature = "jiff-0_2", not(feature = "chrono")))]
8+
use jiff::Timestamp;
9+
#[cfg(feature = "chrono")]
10+
use chrono::{DateTime, Utc};
11+
712
use crate::{
813
Response, ServerResult, Value,
914
extensions::{
@@ -12,13 +17,56 @@ use crate::{
1217
value,
1318
};
1419

20+
// Type alias for timestamp type based on enabled features
21+
#[cfg(all(feature = "jiff-0_2", not(feature = "chrono")))]
22+
type TimestampType = Timestamp;
23+
#[cfg(feature = "chrono")]
24+
type TimestampType = DateTime<Utc>;
25+
26+
// Helper functions to abstract timestamp operations
27+
#[cfg(all(feature = "jiff-0_2", not(feature = "chrono")))]
28+
mod timestamp_ops {
29+
use jiff::Timestamp;
30+
31+
pub fn now() -> Timestamp {
32+
Timestamp::now()
33+
}
34+
35+
pub fn to_rfc3339(ts: &Timestamp) -> String {
36+
ts.to_string()
37+
}
38+
39+
pub fn duration_nanos(start: &Timestamp, end: &Timestamp) -> Option<i64> {
40+
let duration = end.duration_since(*start);
41+
// SignedDuration::as_nanos() returns i128, convert to i64
42+
duration.as_nanos().try_into().ok()
43+
}
44+
}
45+
46+
#[cfg(feature = "chrono")]
47+
mod timestamp_ops {
48+
use chrono::{DateTime, Utc};
49+
50+
pub fn now() -> DateTime<Utc> {
51+
Utc::now()
52+
}
53+
54+
pub fn to_rfc3339(ts: &DateTime<Utc>) -> String {
55+
ts.to_rfc3339()
56+
}
57+
58+
pub fn duration_nanos(start: &DateTime<Utc>, end: &DateTime<Utc>) -> Option<i64> {
59+
(*end - *start).num_nanoseconds()
60+
}
61+
}
62+
1563
struct ResolveState {
1664
path: Vec<String>,
1765
field_name: String,
1866
parent_type: String,
1967
return_type: String,
20-
start_time: DateTime<Utc>,
21-
end_time: DateTime<Utc>,
68+
start_time: TimestampType,
69+
end_time: TimestampType,
2270
start_offset: i64,
2371
}
2472

@@ -32,7 +80,7 @@ impl Serialize for ResolveState {
3280
map.serialize_entry("startOffset", &self.start_offset)?;
3381
map.serialize_entry(
3482
"duration",
35-
&(self.end_time - self.start_time).num_nanoseconds(),
83+
&timestamp_ops::duration_nanos(&self.start_time, &self.end_time),
3684
)?;
3785
map.end()
3886
}
@@ -53,17 +101,17 @@ impl ExtensionFactory for ApolloTracing {
53101
fn create(&self) -> Arc<dyn Extension> {
54102
Arc::new(ApolloTracingExtension {
55103
inner: Mutex::new(Inner {
56-
start_time: Utc::now(),
57-
end_time: Utc::now(),
104+
start_time: timestamp_ops::now(),
105+
end_time: timestamp_ops::now(),
58106
resolves: Default::default(),
59107
}),
60108
})
61109
}
62110
}
63111

64112
struct Inner {
65-
start_time: DateTime<Utc>,
66-
end_time: DateTime<Utc>,
113+
start_time: TimestampType,
114+
end_time: TimestampType,
67115
resolves: Vec<ResolveState>,
68116
}
69117

@@ -79,21 +127,21 @@ impl Extension for ApolloTracingExtension {
79127
operation_name: Option<&str>,
80128
next: NextExecute<'_>,
81129
) -> Response {
82-
self.inner.lock().await.start_time = Utc::now();
130+
self.inner.lock().await.start_time = timestamp_ops::now();
83131
let resp = next.run(ctx, operation_name).await;
84132

85133
let mut inner = self.inner.lock().await;
86-
inner.end_time = Utc::now();
134+
inner.end_time = timestamp_ops::now();
87135
inner
88136
.resolves
89137
.sort_by(|a, b| a.start_offset.cmp(&b.start_offset));
90138
resp.extension(
91139
"tracing",
92140
value!({
93141
"version": 1,
94-
"startTime": inner.start_time.to_rfc3339(),
95-
"endTime": inner.end_time.to_rfc3339(),
96-
"duration": (inner.end_time - inner.start_time).num_nanoseconds(),
142+
"startTime": timestamp_ops::to_rfc3339(&inner.start_time),
143+
"endTime": timestamp_ops::to_rfc3339(&inner.end_time),
144+
"duration": timestamp_ops::duration_nanos(&inner.start_time, &inner.end_time),
97145
"execution": {
98146
"resolvers": inner.resolves
99147
}
@@ -111,13 +159,13 @@ impl Extension for ApolloTracingExtension {
111159
let field_name = info.path_node.field_name().to_string();
112160
let parent_type = info.parent_type.to_string();
113161
let return_type = info.return_type.to_string();
114-
let start_time = Utc::now();
115-
let start_offset = (start_time - self.inner.lock().await.start_time)
116-
.num_nanoseconds()
117-
.unwrap();
162+
let start_time = timestamp_ops::now();
163+
let query_start_time = self.inner.lock().await.start_time;
164+
let start_offset = timestamp_ops::duration_nanos(&query_start_time, &start_time)
165+
.unwrap_or(0);
118166

119167
let res = next.run(ctx, info).await;
120-
let end_time = Utc::now();
168+
let end_time = timestamp_ops::now();
121169

122170
self.inner.lock().await.resolves.push(ResolveState {
123171
path,

src/types/connection/cursor.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ impl CursorType for chrono::DateTime<chrono::Utc> {
129129
}
130130
}
131131

132+
#[cfg(feature = "jiff-0_2")]
133+
impl CursorType for jiff::Timestamp {
134+
type Error = jiff::Error;
135+
136+
fn decode_cursor(s: &str) -> Result<Self, Self::Error> {
137+
s.parse()
138+
}
139+
140+
fn encode_cursor(&self) -> String {
141+
self.to_string()
142+
}
143+
}
144+
132145
#[cfg(feature = "uuid")]
133146
impl CursorType for uuid::Uuid {
134147
type Error = uuid::Error;

src/types/external/bson.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,35 @@ impl ScalarType for UtcDateTime {
6666
}
6767
}
6868

69+
#[cfg(all(feature = "jiff-0_2", not(feature = "chrono")))]
70+
#[Scalar(internal, name = "DateTime")]
71+
impl ScalarType for UtcDateTime {
72+
fn parse(value: Value) -> InputValueResult<Self> {
73+
match &value {
74+
Value::String(s) => {
75+
// Parse as jiff::Timestamp, then convert to bson::DateTime via milliseconds
76+
let timestamp: jiff::Timestamp = s
77+
.parse()
78+
.map_err(|e: jiff::Error| InputValueError::custom(e.to_string()))?;
79+
let millis = timestamp
80+
.as_millisecond()
81+
.try_into()
82+
.map_err(|_| InputValueError::custom("timestamp out of range"))?;
83+
Ok(UtcDateTime::from_millis(millis))
84+
}
85+
_ => Err(InputValueError::expected_type(value)),
86+
}
87+
}
88+
89+
fn to_value(&self) -> Value {
90+
// Convert bson::DateTime to jiff::Timestamp via milliseconds
91+
let millis = self.timestamp_millis();
92+
let timestamp = jiff::Timestamp::from_millisecond(millis)
93+
.expect("valid BSON timestamp should convert to jiff::Timestamp");
94+
Value::String(timestamp.to_string())
95+
}
96+
}
97+
6998
#[Scalar(internal, name = "JSON")]
7099
impl ScalarType for Bson {
71100
fn parse(value: Value) -> InputValueResult<Self> {

src/types/external/chrono_tz.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
44

55
#[Scalar(
66
internal,
7-
name = "TimeZone",
7+
name = "ChronoTz",
88
specified_by_url = "http://www.iana.org/time-zones"
99
)]
1010
impl ScalarType for Tz {

src/types/external/datetime.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
77
/// The input/output is a string in RFC3339 format.
88
#[Scalar(
99
internal,
10-
name = "DateTime",
10+
name = "ChronoDateTimeFixedOffset",
1111
specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339"
1212
)]
1313
impl ScalarType for DateTime<FixedOffset> {
@@ -28,7 +28,7 @@ impl ScalarType for DateTime<FixedOffset> {
2828
/// The input/output is a string in RFC3339 format.
2929
#[Scalar(
3030
internal,
31-
name = "DateTime",
31+
name = "ChronoDateTimeLocal",
3232
specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339"
3333
)]
3434
impl ScalarType for DateTime<Local> {
@@ -49,7 +49,7 @@ impl ScalarType for DateTime<Local> {
4949
/// The input/output is a string in RFC3339 format.
5050
#[Scalar(
5151
internal,
52-
name = "DateTime",
52+
name = "ChronoDateTime",
5353
specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339"
5454
)]
5555
impl ScalarType for DateTime<Utc> {

src/types/external/duration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
99
/// The input/output is a string in ISO8601 format.
1010
#[Scalar(
1111
internal,
12-
name = "Duration",
12+
name = "ChronoDuration",
1313
specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations"
1414
)]
1515
impl ScalarType for Duration {

0 commit comments

Comments
 (0)