Skip to main content

juniper/integrations/
chrono_tz.rs

1//! GraphQL support for [`chrono-tz`] crate types.
2//!
3//! # Supported types
4//!
5//! | Rust type | Format             | GraphQL scalar   |
6//! |-----------|--------------------|------------------|
7//! | [`Tz`]    | [IANA database][1] | [`TimeZone`][s1] |
8//!
9//! [`chrono-tz`]: chrono_tz
10//! [`Tz`]: chrono_tz::Tz
11//! [1]: http://www.iana.org/time-zones
12//! [s1]: https://graphql-scalars.dev/docs/scalars/time-zone
13
14use crate::graphql_scalar;
15
16// TODO: Try remove on upgrade of `chrono-tz` crate.
17mod for_minimal_versions_check_only {
18    use regex as _;
19}
20
21/// Timezone based on [`IANA` database][0].
22///
23/// See ["List of tz database time zones"][3] `TZ database name` column for
24/// available names.
25///
26/// [`TimeZone` scalar][1] compliant.
27///
28/// See also [`chrono_tz::Tz`][2] for details.
29///
30/// [0]: https://www.iana.org/time-zones
31/// [1]: https://graphql-scalars.dev/docs/scalars/time-zone
32/// [2]: https://docs.rs/chrono-tz/*/chrono_tz/enum.Tz.html
33/// [3]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
34#[graphql_scalar]
35#[graphql(
36    with = tz,
37    parse_token(String),
38    specified_by_url = "https://graphql-scalars.dev/docs/scalars/time-zone",
39)]
40pub type TimeZone = chrono_tz::Tz;
41
42mod tz {
43    use super::TimeZone;
44
45    pub(super) fn to_output(v: &TimeZone) -> &'static str {
46        v.name()
47    }
48
49    pub(super) fn from_input(s: &str) -> Result<TimeZone, Box<str>> {
50        s.parse::<TimeZone>()
51            .map_err(|e| format!("Failed to parse `TimeZone`: {e}").into())
52    }
53}
54
55#[cfg(test)]
56mod test {
57    use super::TimeZone;
58
59    mod from_input_value {
60        use super::TimeZone;
61
62        use crate::{FromInputValue, InputValue, IntoFieldError, graphql_input_value};
63
64        fn tz_input_test(raw: &'static str, expected: Result<TimeZone, &str>) {
65            let input: InputValue = graphql_input_value!((raw));
66            let parsed = FromInputValue::from_input_value(&input);
67
68            assert_eq!(
69                parsed.as_ref(),
70                expected.map_err(IntoFieldError::into_field_error).as_ref(),
71            );
72        }
73
74        #[test]
75        fn europe_zone() {
76            tz_input_test("Europe/London", Ok(chrono_tz::Europe::London));
77        }
78
79        #[test]
80        fn etc_minus() {
81            tz_input_test("Etc/GMT-3", Ok(chrono_tz::Etc::GMTMinus3));
82        }
83
84        mod invalid {
85            use super::tz_input_test;
86
87            #[test]
88            fn forward_slash() {
89                tz_input_test(
90                    "Abc/Xyz",
91                    Err("Failed to parse `TimeZone`: failed to parse timezone"),
92                );
93            }
94
95            #[test]
96            fn number() {
97                tz_input_test(
98                    "8086",
99                    Err("Failed to parse `TimeZone`: failed to parse timezone"),
100                );
101            }
102
103            #[test]
104            fn no_forward_slash() {
105                tz_input_test(
106                    "AbcXyz",
107                    Err("Failed to parse `TimeZone`: failed to parse timezone"),
108                );
109            }
110        }
111    }
112}